nodejs - Computer | Web | Back_end
nodejs
前言 Preface
概述 Overview
什么是 JS?
- 脚本语言
- 运行在浏览器内核中的 JS 引擎(engine)
- 一般用来做客户端页面的交互(Interactive)
浏览器中的 JS 可以做什么?
- 操作 DOM(对 DOM 的增删改、注册事件、跨域)
- AJAX/跨域
- BOM(页面跳转、历史记录、console.log()、alert())
- ECMAScript
浏览器中的 JS 不可以做什么?为什么?
- 不能进行文件操作(文件和文件夹的增删改查)
- 没有办法操作系统信息
- 由于运行环境特殊(我们写的代码是在不认识的人的浏览器中运行,会产生安全、隐私等问题)
什么是 node.js?
定义
Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world.
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。
Node.js 的包管理器 npm,是全球最大的开源库生态系统。
- Node 是 Javascript 语言在服务器端的运行环境(runtime),不是一门语言,也不是 Javascript 语言的一个库。所谓“运行环境(平台)”有两层意思:
- 首先,Javascript 语言通过 Node 在服务器运行,在这个意义上,Node 有点像 Javascript 的虚拟机;
- 其次,Node 提供大量工具库,使得 JavaScript 语言与操作系统互动(比如读写文件、新建子进程),在这个意义上, Node 又是 Javascript 的工具库。
- 虽然 Node.js 是以 .js 为后缀结尾的,但实际上 Node.js 封装了谷歌 chrome 浏览器的 v8 引擎(v8 是由 c++ 语言编写的,执行 Javascript 的速度非常快,性能非常好。),本质上是一个 Javascript 的运行环境。
分类
特点
事件驱动
非阻塞 I/O
单线程
优点 - 相对其他后台语言
- Node.js 的核心语法前后端通用,减少了程序员对前后端语言的切换;
- Node.js 对一些特殊用例进行了优化,提供了替代的 API,使得 V8 引擎在非浏览器环境下运行得更好;
- Node.js 用于方便地搭建响应速度快、易于扩展的网络应用;
- Node.js 使用事件驱动,非阻塞 I/O 模型而得以轻量和高效,非常适合开发在分布式设备上运行的数据密集型的实时应用。
缺点
QA
Node 为什么选择 Javascript?
- Javascript 目前是开发行业中最火热的一门语言,会的人很多很多;
- chrome 的 V8 engine 效率高。
编程语言的能力取决于什么是?
- 语言本身?
- 语言本身只是提供变量、函数、类型等的定义,以及流程控制,循环结构之类的操作。
- 取决于运行该语言的平台(环境)
- 对于 JS 来说,我们常说的 JS 实际是 ES,大部分能力都是由浏览器的执行引擎(在 Chrome 中即 V8)决定,BOM 和 DOM 可以说是浏览器开放出来的接口,比如:
- Cordova 中提供 JS 调用摄像头
- 操作本地文件的 API
语言和运行环境(平台)的关系
- Node.js 是平台,Javascript 是语言
- Java 既是语言也是平台(Java 运行在 Java 虚拟机,跨操作系统)
- PHP 既是语言也是平台(跨操作系统)
- C# 语言平台:.NET Framework(Windows),也可以运行在 MONO 这样的平台( Linux)
ABOUT
About Node.js®
As an asynchronous event driven(异步事件驱动) JavaScript runtime, Node is designed to build scalable network applications. In the following "hello world" example, many connections(来自多个不同客户端的连接) can be handled concurrently. Upon each connection the callback is fired, but if there is no work to be done, Node will sleep.
const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});This is in contrast to today's more common concurrency model where OS threads(多线程) are employed. Thread-based networking is relatively inefficient and very difficult to use. Furthermore, users of Node are free from worries of dead-locking the process(进程死锁), since there are no locks. Almost no function in Node directly performs I/O, so the process never blocks. Because nothing blocks, scalable systems are very reasonable to develop in Node.
If some of this language is unfamiliar, there is a full article onBlocking vs Non-Blocking.
Node is similar in design to, and influenced by, systems like Ruby's Event Machine or Python's Twisted. Node takes the event model a bit further. It presents an event loop as a runtime construct instead of as a library. In other systems there is always a blocking call to start the event-loop. Typically behavior is defined through callbacks at the beginning of a script and at the end starts a server through a blocking call like EventMachine::run(). In Node there is no such start-the-event-loop call. Node simply enters the event loop after executing the input script. Node exits the event loop when there are no more callbacks to perform. This behavior is like browser JavaScript — the event loop is hidden from the user.
HTTP is a first class citizen in Node, designed with streaming and low latency in mind. This makes Node well suited for the foundation of a web library or framework.
Just because Node is designed without threads, doesn't mean you cannot take advantage of multiple cores in your environment. Child processes can be spawned by using our child_process.fork() API, and are designed to be easy to communicate with. Built upon that same interface is the cluster module, which allows you to share sockets between processes to enable load balancing over your cores.
异步编程
什么是异步
现实生活中
程序世界里
异步方式
回调函数
对于一个函数如果需要定义回调函数:
回调函数一定作为参数的最后一个参数出现:
function foo1(name, age, callback) { }
function foo2(value, callback1, callback2) { }
回调函数的第一个参数默认接收错误信息,第二个参数才是真正的回调数据(便于外界获取调用的错误情况):
foo1('赵小黑', 19, function(error, data) {
if(error) throw error;
console.log(data);
});
Node 统一约定 强调 错误优先 因为之后的操作大多数都是异步的方式,无法通过 try catch 捕获异常, SO 错误优先的回调函数: 第一个参数为上一步的错误信息
回调函数的问题
相比较于传统的代码,异步事件驱动的代码: 不容易阅读 不容易调试 不容易维护 回调嵌套过深: do1(function() {
do2(function() {
do3(function() {
do4(function() {
do5(function() {
do6()
});
});
});
});
});
可能产生回调黑洞的问题
promise
async
什么是 I/O
进程和线程
什么是进程?(进行中的程序)
每一个 正在运行 的应用程序都称之为进程
每一个应用程序运行都至少有一个进程
进程是用来 给应用程序提供的一个运行环境
进程是操作系统为应用程序分配资源的一个单位
什么是线程?
用来执行应用程序中的代码
在一个进程内部,可以有很多的线程
在一个线程内部,同时只可以干一件事
而且传统的开发方式大部分都是 I/O 阻塞的
所以需要多线程来更好的利用硬件资源
给人带来一种错觉:线程越多越好
什么原因让多线程没落?
多线程都是假的,因为只一个 CPU(单核)
线程之间共享某些数据,同步某个状态都很麻烦
更致命的是:
创建线程耗费资源
线程数量有限
CPU 在不同线程之间转换,有个上下文转换,这个转换非常耗时
事件驱动
非阻塞 I/O
非阻塞的优势
提高代码的响应效率
充分利用单核 CPU 的优势
改善 I/O 的不可预测带来的问题
如何提高一个人的工作效率
但是目前市面上大多数都是多核 CPU
Node 的实现结构
chrome's V8 engine
libuv
API
历史
浏览器大战
现代浏览器大战
node 诞生
2008年左右,随着 AJAX 的逐渐普及,Web 开发逐渐走向复杂化,系统化;
2009年2月,Ryan Dahl 想要创建一个轻量级,适应现代 Web 开发的平台;
2009年5月,Ryan Dahl 在 GitHub 中开源了最初版本,同年11月的 JSConf 就安排了 Node 讲座;
2010年底,Joyent 公司资助,Ryan Dahl 也加入了该公司,专门负责 Node 的开发;
2011年7月,在微软的支持下登陆 Windows 平台;
浏览器大战和 Node 有何关系?
优势
在 Node 上运行的 JavaScript 相比其他后端开发语言有何优势?
非阻塞--单线程--事件驱动--高并发--密集 IO
应用
做网站
分发数据请求,渲染 HTML
开发服务端应用程序
Web 系统
基于 Node 的前端工具集
环境配置
安装 Node.js
- Node Node.js --- 类似一个操作系统
+ 一个JS的运行环境
+ 主要用于开发 Web 应用程序(回想登陆的例子)
+ 很多的前端开发工具都是基于 node 这个平台
+ 所用的工具就相当于一些软件
node.js 的版本
安装包的方式(不推荐)
下载安装包:
Darwin:链接
Windows:x64 / x86
安装过程:
Next
Next
Next
… 更新 Node 版本: 操作方式: 重新下载最新的安装包; 覆盖安装即可; 问题: 以前版本安装的很多全局的工具包需要重新安装 无法回滚到之前的版本 无法在多个版本之间切换(很多时候我们要使用特定版本)
NVM 的方式
安装 NVM
### NVM工具的使用
> Node Version Manager(Node 版本管理工具)
由于以后的开发工作可能会在多个 Node 版本中测试,而且 Node 的版本也比较多,所以需要这么款工具来管理
#### 安装操作步骤
1. 下载:[nvm-windows](https://github.com/coreybutler/nvm-windows/releases/download/1.1.0/nvm-noinstall.zip)
2. 解压到一个全英文路径
3. 编辑解压目录下的`settings.txt`文件(不存在则新建)
+ `root 配置为当前 nvm.exe 所在目录`
+ `path 配置为 node 快捷方式所在的目录`
+ `arch 配置为当前操作系统的位数(32/64)`
+ `proxy 不用配置`
4. 配置环境变量 可以通过 window+r : sysdm.cpl
+ `NVM_HOME = 当前 nvm.exe 所在目录`
+ `NVM_SYMLINK = node 快捷方式所在的目录`
+ `PATH += %NVM_HOME%;%NVM_SYMLINK%;`
+ 打开CMD通过`set [name]`命令查看环境变量是否配置成功
+ PowerShell中是通过`dir env:[name]`命令
5. NVM使用说明:
+ https://github.com/coreybutler/nvm-windows/
6. NPM的目录之后使用再配置
安装 NPM
- NPM
+ node package manager ( node 的包管理工具)
+ npm 管理包非常方便,我们只需要记住使用哪个包就可以了
+ 使用
- npm install xxx
- 安装一个包到项目本地,必须要联网
- 安装完成过后项目根目录下会多一个node_modules文件夹,所有的下载下来的包全部在里面
- 由于需要记录项目依赖哪些东西,所以需要一个配置文件 “package.json”,可以通过 npm init 命令生成
- 以后安装包的时候将其--save
- --save 就是将我们安装的包名字和包版本记录到配置文件中的 dependencies 节点中
- --save-dev
- 项目依赖分两种,一个就是普通的项目依赖比如 bootstrap ,还用一种只是开发阶段需要用的,这种属于开发依赖比如gulp,开发依赖最终记录在devDependencies节点里面
- npm uninstall xxx --save
- npm install xxx -g (全局安装包)
+ 如果你安装的是一个工具,工具要在每一个地方都能用,这种情况下一般全局安装。
什么是包
由于 Node 是一套轻内核的平台,虽然提供了一系列的内置模块,但是不足以满足开发者的需求,于是乎出现了包(package)的概念:
与核心模块类似,就是将一些预先设计好的功能或者说 API 封装到一个文件夹,提供给开发者使用;
id: 包名的情况:require('http')
先在系统核心(优先级最高)的模块中找;
以后不要创建一些和现有的包重名的包;
然后再到当前项目中 node_modules 目录中找;由于 Node 本身并没有太多的功能性 API,所以市面上涌现出大量的第三方人员开发出来的 Package
包的生态圈一旦繁荣起来,就必须有工具去代替人脑或者文档的方式管理
什么是 NPM
随着时间的发展,NPM 出现了两层概念:
一层含义是 Node 的开放式模块登记和管理系统,亦可以说是一个生态圈,一个社区
另一层含义是 Node 默认的模块管理器,是一个命令行下的软件,用来安装和管理 Node 模块。
官方链接: https://www.npmjs.com/
国内加速镜像: https://npm.taobao.org/
可以通过 NRM: Node Registry Manager
为什么使用 NPM
- 包很多
- 场景:我需要用一个A,A依赖B,B依赖C
- 常见的包管理工具都有循环依赖的功能
- 你只需记住你要什么东西
安装 NPM
NPM 不需要单独安装。默认在安装 Node 的时候,会连带一起安装 NPM。
但是,Node 附带的 NPM 可能不是最新版本,最好用下面的命令,更新到最新版本。
$ npm install npm -g
默认安装到当前系统 Node 所在目录下。
由于之前使用 NVM 的方式安装的 Node 所以需要重新配置 NPM 的全局目录
配置 NPM 的全局目录
$ npm config set prefix [pathtonpm]
将 NPM 目录配置到其他目录时,必须将该目录放到环境变量中,否则无法再全局使用
常见的 NPM 操作
npm config
npm init
npm search
npm info
npm install
npm uninstall
npm list
npm outdated
npm update
npm run
npm cache [clean|ls]
https://docs.npmjs.com/
安装 NRM
环境变量
### 环境变量的概念
> 环境变量就是操作系统提供的系统级别用于存储变量的地方
- Windows中环境变量分为系统变量和用户变量
- 环境变量的变量名是不区分大小写的
- 特殊值:
+ PATH 变量:只要添加到 PATH 变量中的路径,都可以在任何目录下搜索
Windows 常用命令行操作
### Windows 下常用的命令行操作
- 切换当前目录(change directory):cd
- 创建目录(make directory):mkdir
- 查看当前目录列表(directory):dir
+ 别名:ls(list)
- 清空当前控制台:cls
+ 别名:clear
- 删除文件:del
+ 别名:rm
> 注意:所有别名必须在新版本的 PowerShell 中使用
如何学习Node.js
二、www.npmjs.com 模块社区,看他人源代码,省力
三、github.com 大量的项目和源码,阅读优秀的源码是一种很快的提高学习速度的方法
四、stackoverflow.com 技术解答社区以及查询相关资源,环境配置,异常均可找到答案
搭建 Node 开发环境
文本编辑器
命令行模式和 Node 交互模式
使用严格模式
node --use_strict calc.js
基础概念
Node 命令的基本用法
进入 REPL 环境:
$ node
执行脚本字符串:
$ node -e 'console.log("Hello World")'
运行脚本文件:
$ node index.js
$ node path/index.js
$ node path/index
查看帮助:
$ node --help
REPL 环境
REPL 环境操作
进入 REPL:
node
node --use_strict
REPL 环境中:
类似 Chrome Developer Tools → Console
特殊变量下划线(_)表示上一个命令的返回结果
通过 .exit 或执行 process.exit() 退出 REPL 交互
全局作用域成员
全局对象
global:
类似于客户端 JavaScript 运行环境中的 window
process:
用于获取当前的 Node 进程信息,一般用于获取环境变量之类的信息
console:
Node 中内置的 console 模块,提供操作控制台的输入输出功能,常见使用方式与客户端类似
全局变量
全局函数
setInterval(callback, millisecond)
clearInterval(timer)
setTimeout(callback, millisecond)
clearTimeout(timer)
Buffer:Class
用于操作二进制数据
异步操作之回调函数
Node 调试
最方便也是最简单的:console.log()
Node 原生的调试
https://nodejs.org/api/debugger.html
第三方模块提供的调试工具
$ npm install node-inspector –g
$ npm install devtool -g
开发工具的调试
推荐 Visual Studio Code
WebStorm
node API ---
文件系统操作
相关模块
fs:
基础的文件操作 API
path:
提供和路径相关的操作 API
*readline:
用于读取大文本文件,一行一行读
fs-extra(第三方):
https://www.npmjs.com/package/fs-extra
fs 模块
fs 模块对文件的几乎所有操作都有同步和异步两种形式
例如:readFile() 和 readFileSync()
区别:
同步调用会阻塞代码的执行,异步则不会
异步调用会将读取任务下达到任务队列,直到任务执行完成才会回调
异常处理方面,同步必须使用 try catch 方式,异步可以通过回调函数的第一个参数
文件读取
异步文件读取
fs.readFile(file[,options],callback(err,data))
同步文件读取
fs.readFileSync(file,[,option])
文件流的方式读取(后面单独介绍)
fs.createReadStream(path[, options])
文件写入
确保操作没有额外的问题,一定使用绝对路径的方式
异步文件写入
fs.writeFile(file,data[,option],callback(err))
同步文件写入
fs.writeFileSync(file,data,[,option])
流式文件写
fs.createWriteStream(path[,option])
默认写入操作是覆盖源文件
文件追加
异步追加
fs.appendFile(file,data[,options],callback(err))
同步追加
fs.appendFileSync(file,data[,options])
目录操作
创建一个目录
fs.mkdir(path[,model],callback)
fs.mkdirSync(path[,model])
删除一个空目录
fs.rmdir(path,callback)
fs.rmdirSync(path)
读取一个目录
fs.readdir(path,callback(err,files))
fs.readdirSync(path) // => 返回files
监视文件
监视文件变化:
fs.watchFile(filename[, options], listener(curr,prev))
options:{persistent,interval}
fs.watch(filename[,options][,listener]) 文件监视案例 利用文件监视实现自动 markdown 文件转换 https://github.com/chjj/marked https://github.com/Browsersync/browser-sync 作业: 实现 less 自动转换
其它文件操作
验证路径是否存在(过时的API)
fs.exists(path,callback(exists))
fs.existsSync(path) // => 返回布尔类型 exists
获取文件信息
fs.stat(path,callback(err,stats))
fs.statSync(path) // => 返回一个fs.Stats实例
移动文件
fs.rename(oldPath,newPath) 重命名文件或目录 fs.rename(oldPath,newPath,callback) fs.renameSync(oldPath,newPath) 删除文件 fs.unlink(path,callback(err)) fs.unlinkSync(path)
路径操作模块(path)
在文件操作的过程中,都”必须”使用物理路径(绝对路径)
path 模块提供了一系列与路径相关的 API
path.join([p1][,p2][,p3]…) => 连接多个路径
path.basename(p, ext) => 获取文件名
path.dirname(p) => 获取文件夹路径
path.extname(p) => 获取文件扩展名
path.format(obj) 和 path.parse(p)
path.relative(from, to) => 获取从 from 到 to 的相对路径
源码地址:
https://github.com/nodejs/node/blob/master/lib/path.js
readline 模块
const readline = require('readline');
const fs = require('fs');
const rl = readline.createInterface({
input: fs.createReadStream('sample.txt')
});
rl.on('line', (line) => {
console.log('Line from file:', line);
});缓冲区处理(二进制数据)
为什么要有缓冲区
JS 是比较擅长处理字符串,但是早期的应用场景主要用于处理 HTML 文档,不会有太大篇幅的数据处理,也不会接触到二进制的数据。
而在 Node 中操作数据、网络通信是没办法完全以字符串的方式操作的。
所以在 Node 中引入了一个二进制的缓冲区的实现:Buffer。
创建缓冲区
创建长度为4个字节的缓冲区
var buffer = new Buffer(4);
通过指定数组内容的方式创建
var buffer = new Buffer([00,01]);
通过指定编码的方式创建
var buffer = new Buffer('hello', 'utf8');
Node 默认支持的编码
Buffers 和 JavaScript 字符串对象之间转换时需要一个明确的编码方法。下面是字符串的不同编码。
'ascii' - 7位的 ASCII 数据。这种编码方式非常快,它会移除最高位内容。
'utf8' - 多字节编码 Unicode 字符。大部分网页和文档使用这类编码方式。
'utf16le' - 2个或4个字节, Little Endian (LE) 编码 Unicode 字符。编码范围 (U+10000 到 U+10FFFF) 。
'ucs2' - 'utf16le'的子集。
'base64' - Base64 字符编码。
'binary' - 仅使用每个字符的头8位将原始的二进制信息进行编码。在需使用 Buffer 的情况下,应该尽量避免使用这个已经过时的编码方式。这个编码方式将会在未来某个版本中弃用。
'hex' - 每个字节都采用 2 进制编码。
文件流
什么是流?
文件流、网络流
任何数据的最根本表现形式都是二进制的 文件流就是以面向对象的概念对文件数据进行的抽象 文件流定义了一些对文件数据的操作方式
网络操作
相关模块
url
用于解析 URL 格式的模块
querystring
用于操作类似 k1=v1&k2=v2 的查询字符串
http
用于创建 HTTP 服务器或 HTTP 客户端
URL 解析模块(url)
将一个 URL 字符串解析为一个 URL 对象
url.parse(urlStr[, parseQueryString][, slashesDenoteHost])
将一个 URL 对象格式化为字符串的形式
url.format(urlObj)
用于组合 URL 成员为完整的 URL 字符串
url.resolve(from, to)
查询字符串模块(querystring)
querystring.escape
querystring.parse(str[, sep][, eq][, options])
querystring.stringify(obj[, sep][, eq][, options])
querystring.unescape
HTTP 服务模块(http)
CommonJS 规范
概述
CommonJS 就是一套约定标准,不是技术;
用于约定我们的代码应该是怎样的一种结构;
特点
所有代码都运行在模块作用域,不会污染全局作用域。
模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
模块加载的顺序,按照其在代码中出现的顺序。
模块加载机制 - 深入了解 CommonJS 原理
大量使用全局变量带来的问题
var s = 'global';闭包解决该问题并实现模块隔离
var s = 'Hello';
var name = 'world';
console.log(s + ' ' + name + '!');(function () {
// 读取的hello.js代码:
var s = 'Hello';
var name = 'world';
console.log(s + ' ' + name + '!');
// hello.js代码结束
})();模块的输出 module.exports 怎么实现?
// 准备module对象:
var module = {
id: 'hello',
exports: {}
};
var load = function (module) {
// 读取的hello.js代码:
function greet(name) {
console.log('Hello, ' + name + '!');
}
module.exports = greet;
// hello.js代码结束
return module.exports;
};
var exported = load(module);
// 保存module:
save(module, exported);module.exports = greet;var greet = require('./hello');module.exports vs exports
// hello.js
function hello() {
console.log('Hello, world!');
}
function greet(name) {
console.log('Hello, ' + name + '!');
}
module.exports = {
hello: hello,
greet: greet
};// hello.js
function hello() {
console.log('Hello, world!');
}
function greet(name) {
console.log('Hello, ' + name + '!');
}
exports.hello = hello;
exports.greet = greet;// 代码可以执行,但是模块并没有输出任何变量:
exports = {
hello: hello,
greet: greet
};var module = {
id: 'hello',
exports: {}
};load() 函数最终返回 module.exports:
var load = function (exports, module) {
// hello.js的文件内容
...
// load函数返回:
return module.exports;
};
var exported = load(module.exports, module);exports.foo = function () { return 'foo'; };
exports.bar = function () { return 'bar'; };module.exports.foo = function () { return 'foo'; };
module.exports.bar = function () { return 'bar'; };module.exports = function () { return 'foo'; };结论
module.exports = {
foo: function () { return 'foo'; }
};module.exports = function () { return 'foo'; };module.exports = variable;
var foo = require('other_module');模块化结构
模块化的好处
因为 javascript 天生缺少一种模块管理机制 来隔离实现不同功能的 JS 片段;避免他们相互污染;为此我们经常采用命名空间的方法,把变量和函数限制在某个特定的作用域内;人肉约定一套命名规范来约束代码,从而保证代码的安全执行。如 jQuery 里面有许许多多的变量和方法,但是你是直接访问不到的,必须通过jQuery 或 $ 符号来调用里面的变量或方法;
模块的分类
文件模块
就是我们自己写的功能模块文件
核心模块
Node 平台自带的一套基本的功能模块,也有人称之为 Node 平台的 API
第三方模块
社区或第三方个人开发好的功能模块,可以直接拿回来用
模块化开发的流程
模块的定义
一个新的 JS 文件就是一个模块;
一个合格的模块应该是有导出成员的,否则模块就失去了定义的价值;
模块内部是一个独立(封闭)的作用域(模块与模块之间不会冲突);
模块之间必须通过导出或导入的方式协同;
导出方式:
exports.name = value;
module.exports = { name: value };
module.exports 和 exports
module.exports 是用于为模块导出成员的接口
exports 是指向 module.exports 的别名,相当于在模块开始的时候执行:
var exports = module.exports;
一旦为 module.exports 赋值,就会切断之前两者的相关性;
最终模块的导出成员以 module.exports 为准
编写模块
'use strict';
var s = 'Hello';
function greet(name) {
console.log(s + ', ' + name + '!');
}
module.exports = greet;'use strict';
// 引入hello模块:
var greet = require('./hello');
var s = 'Michael';
greet(s); // Hello, Michael!var greet = require('./hello');var greet = require('hello');模块内全局环境(伪)
我们在之后的文件操作中必须使用绝对路径__dirname
用于获取当前文件所在目录的完整路径;
在 REPL 环境无效;__filename
用来获取当前文件的完整路径;
在 REPL 环境同样无效;module
模块对象 Node 内部提供一个 Module 构建函数。所有模块都是 Module 的实例,属性如下: module.id 模块的识别符,通常是带有绝对路径的模块文件名。 module.filename 模块定义的文件的绝对路径。 module.loaded 返回一个布尔值,表示模块是否已经完成加载。 module.parent 返回一个对象,表示调用该模块的模块。 module.children 返回一个数组,表示该模块要用到的其他模块。 module.exports 表示模块对外输出的值。 载入一个模块就是构建一个 Module 实例。exports
映射到 module.exports 的别名require()
require.cache require.extensions require.main require.resolve()
模块的加载过程 - require 函数
require 函数 - 用于加载模块文件
Node 使用 CommonJS 模块规范,内置的 require 函数用于加载模块文件。
require 的基本功能是,读入并执行一个 JavaScript 文件,然后返回该模块的 exports 对象。
如果没有发现指定模块,会报错。
require 扩展名
require 加载文件时可以省略扩展名:
require('./module');
// 此时文件按 JS 文件执行
require('./module.js');
// 此时文件按 JSON 文件解析
require('./module.json');
// 此时文件预编译好的 C++ 模块执行
require('./module.node');
// 载入目录module目录中的 package.json 中 main 指向的文件
require('./module/default.js');
// 载入目录 module 中的 index.js 文件
require 加载文件规则
通过 ./ 或 ../ 开头:则按照相对路径从当前文件所在文件夹开始寻找模块;
require('../file.js'); => 上级目录下找 file.js 文件
通过 / 开头:则以系统根目录开始寻找模块;
require('/Users/iceStone/Documents/file.js'); => 以绝对路径的方式找
没有任何异议 如果参数字符串不以“./“ 或 ”/“ 开头,则表示加载的是一个默认提供的核心模块(位于 Node 的系统安装目录中): require('fs'); => 加载核心模块中的文件系统模块 或者从当前目录向上搜索 node_modules 目录中的文件: require('my_module'); => 各级 node_modules 文件夹中搜索 my_module.js 文件;
require 加载目录规则
如果 require 传入的是一个目录的路径,会自动查看该目录的 package.json 文件,然后加载 main 字段指定的入口文件
如果 package.json 文件没有 main 字段,或者根本就没有 package.json 文件,则默认找目录下的 index.js 文件作为模块:
require('./calcuator'); => 当前目录下找 calculator 目录中的 index.js 文件
模块的缓存
第一次加载某个模块时,Node 会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的 module.exports 属性(不会再次执行该模块)
如果需要多次执行模块中的代码,一般可以让模块暴露行为(函数)
模块的缓存可以通过 require.cache 拿到,同样也可以删除
require 的实现机制
将传入的模块 ID 通过加载规则找到对应的模块文件
读取这个文件里面的代码
通过拼接的方式为该段代码构建私有空间
执行该代码
拿到 module.exports 返回
模块查找过程
核心模块
核心模块的意义
path:处理文件路径。
fs:操作(CRUD)文件系统。
child_process:新建子进程。
util:提供一系列实用小工具。
http:提供 HTTP 服务器功能。
url:用于解析 URL。
querystring:解析 URL 中的查询字符串。
crypto:提供加密和解密功能。
https://nodejs.org/api/
第三方模块
cookies
koa
koa是Express的下一代基于Node.js的web框架,目前有1.x和2.0两个版本。
简介 Introduction
安装 Installation
Koa requires node v7.6.0 or higher for ES2015 and async function support.
You can quickly install a supported version of node with your favorite version manager:
$ nvm install 7
$ npm i koa
$ node my-koa-app.jsAsync Functions with Babel
To use async functions in Koa in versions of node < 7.6, we recommend using babel's require hook.
require('babel-core/register');
// require the rest of the app that needs to be transpiled after the hook
const app = require('./app');To parse and transpile async functions, you should at a minimum have the transform-async-to-generator or transform-async-to-module-method plugins. For example, in your .babelrc file, you should have:
{
"plugins": ["transform-async-to-generator"]
}You can also use the env preset with a target option "node": "current"instead.
应用 Application
Koa 应用是一个包含一系列中间件 函数的对象。 这些中间件函数基于 request 请求以一个类似于栈的结构组成并依次执行。 Koa 类似于其他中间件系统(比如 Ruby's Rack 、Connect 等), 然而 Koa 的核心设计思路是为中间件层提供高级语法糖封装,以增强其互用性和健壮性,并使得编写中间件变得相当有趣。
Koa 包含了像 content-negotiation(内容协商)、cache freshness(缓存刷新)、proxy support(代理支持)和 redirection(重定向)等常用任务方法。 与提供庞大的函数支持不同,Koa只包含很小的一部分,因为Koa并不绑定任何中间件。
任何教程都是从 hello world 开始的,Koa也不例外^_^
const Koa = require('koa');
const app = new Koa();
app.use(ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);中间件级联
Koa 的中间件通过一种更加传统(您也许会很熟悉)的方式进行级联,摒弃了以往 node 频繁的回调函数造成的复杂代码逻辑。 我们通过 async functions 来实现“真正”的中间件。 Connect 简单地将控制权交给一系列函数来处理,直到函数返回。 与之不同,当执行到 await next() 语句时,Koa 暂停了该中间件,继续执行下一个符合请求的中间件('downstrem'),然后控制权再逐级返回给上层中间件('upstream')。
下面的例子在页面中返回 "Hello World",然而当请求开始时,请求先经过 x-response-time 和 logging 中间件,并记录中间件执行起始时间。 然后将控制权交给 reponse 中间件。当中间件运行到 await next() 时,函数挂起并将控制前交给下一个中间件。当没有中间件执行 await next() 时,程序栈会逆序唤起被挂起的中间件来执行接下来的代码。
const Koa = require('koa');
const app = new Koa();
// x-response-time
app.use(async function (ctx, next) {
const start = new Date();
await next();
const ms = new Date() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
// logger
app.use(async function (ctx, next) {
const start = new Date();
await next();
const ms = new Date() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});
// response
app.use(ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);配置
应用配置是 app 实例属性,目前支持的配置项如下:
- app.env 默认为 NODE_ENV 或者 development
- app.proxy 如果为 true,则解析 "Host" 的 header 域,并支持 X-Forwarded-Host
- app.subdomainOffset 默认为2,表示 .subdomains 所忽略的字符偏移量。
app.listen(...)
Koa 应用并非是一个 1-to-1 表征关系的 HTTP 服务器。 一个或多个Koa应用可以被挂载到一起组成一个包含单一 HTTP 服务器的大型应用群。
如下为一个绑定3000端口的简单 Koa 应用,其创建并返回了一个 HTTP 服务器,为 Server#listen() 传递指定参数(参数的详细文档请查看nodejs.org)。The following is a useless Koa application bound to port 3000:
const Koa = require('koa');
const app = new Koa();
app.listen(3000);app.listen(...) 实际上是以下代码的语法糖:
const http = require('http');
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(3000);这意味着您可以同时支持 HTTP 和 HTTPS,或者在多个端口监听同一个应用。
const http = require('http');
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(3000);
http.createServer(app.callback()).listen(3001);app.callback()
返回一个适合 http.createServer() 方法的回调函数用来处理请求。 您也可以使用这个回调函数将您的app挂载在 Connect/Express 应用上。
app.use(function)
为应用添加指定的中间件函数,详情请看 Middleware
app.keys=
设置签名Cookie密钥,该密钥会被传递给 KeyGrip。
当然,您也可以自己生成 KeyGrip 实例:
app.keys = ['im a newer secret', 'i like turtle'];
app.keys = new KeyGrip(['im a newer secret', 'i like turtle'], 'sha256');
在进行cookie签名时,只有设置 signed 为 true 的时候,才会使用密钥进行加密:
this.cookies.set('name', 'tobi', { signed: true });app.context
app.context is the prototype from which ctx is created from. You may add additional properties to ctx by editing app.context. This is useful for adding properties or methods to ctx to be used across your entire app, which may be more performant (no middleware) and/or easier (fewer require()s) at the expense of relying more on ctx, which could be considered an anti-pattern.
For example, to add a reference to your database from ctx:
app.context.db = db();
app.use(async (ctx) => {
console.log(ctx.db);
});Note:
- Many properties on ctx are defined using getters, setters, and Object.defineProperty(). You can only edit these properties (not recommended) by using Object.defineProperty() on app.context. See https://github.com/koajs/koa/issues/652.
- Mounted apps currently use its parent's ctx and settings. Thus, mounted apps are really just groups of middleware.
错误处理
默认情况下Koa会将所有错误信息输出到 stderr,unless app.silent is true. The default error handler also won't outputs errors when err.status is 404 or err.expose is true. 为了实现自定义错误处理逻辑(比如 centralized logging),您可以添加 "error" 事件监听器。
app.on('error', err =>
log.error('server error', err)
);如果错误发生在 请求/响应 环节,并且其不能够响应客户端时,Contenxt实例也会被传递到 error 事件监听器的回调函数里。
app.on('error', (err, ctx) =>
log.error('server error', err, ctx)
);当发生错误但仍能够响应客户端时(比如没有数据写到socket中),Koa会返回一个500错误(Internal Server Error)。
无论哪种情况,Koa都会生成一个应用级别的错误信息,以便实现日志记录等目的。
上下文 Context
Koa Context 将 node 的 request 和 response 对象封装在一个单独的对象里面,其为编写 web 应用和 API 提供了很多有用的方法。
这些操作在 HTTP 服务器开发中经常使用,因此其被添加在上下文这一层,而不是更高层框架中,因此将迫使中间件需要重新实现这些常用方法。
context 在每个 request 请求中被创建,在中间件中作为接收器(receiver)来引用,或者通过 ctx 标识符来引用:
app.use(async (ctx, next) => {
ctx; // is the Context
ctx.request; // is a koa Request
ctx.response; // is a koa Response
});许多 context 的访问器和方法为了便于访问和调用,简单的委托给他们的 ctx.request 和 ctx.response 所对应的等价方法, 比如说 ctx.type 和 ctx.length 委托给了 response,ctx.path 和 ctx.method 委托给了了 request。
API
Context 详细的方法和访问器。
ctx.req
Node 的 request 对象。
ctx.res
Node 的 response 对象。
Koa 不支持 直接调用底层 res 进行响应处理。请避免使用以下 node 属性:
- res.statusCode
- res.writeHead()
- res.write()
- res.end()
ctx.request
Koa 的 Request 对象。
ctx.response
Koa 的 Response 对象。
ctx.state
The recommended namespace for passing information through middleware and to your frontend views.
ctx.state.user = await User.find(id);ctx.app
应用实例引用。
ctx.cookies.get(name, [options])
获得 cookie 中名为 name 的值,options 为可选参数:
- 'signed': 如果为 true,表示请求时 cookie 需要进行签名。
注意:Koa 使用了 Express 的 cookies 模块,options 参数只是简单地直接进行传递。
ctx.cookies.set(name, value, [options])
设置 cookie 中名为 name 的值,options 为可选参数:
- maxAge: a number representing the milliseconds from Date.now() for expiry
- signed: sign the cookie value
- expires: cookie 有效期时间
- path: cookie 的路径,默认为 /'
- domain: cookie 的域
- secure: false 表示 cookie 通过 HTTP 协议发送,true 表示 cookie 通过 HTTPS 发送。
- httpOnly: true 表示 cookie 只能通过 HTTP 协议发送
- overwrite a boolean indicating whether to overwrite previously set cookies of the same name (false by default). If this is true, all cookies set during the same request with the same name (regardless of path or domain) are filtered out of the Set-Cookie header when setting this cookie.
注意:Koa 使用了 Express 的 cookies 模块,options 参数只是简单地直接进行传递。
ctx.throw([status], [msg], [properties])
抛出包含 .status 属性的错误,默认为 500。该方法可以让 Koa 准确的响应处理状态。 Koa支持以下组合:
ctx.throw(400);
ctx.throw(400, 'name required');
ctx.throw(400, 'name required', { user: user });ctx.throw('name required', 400) 等价于:
const err = new Error('name required');
err.status = 400;
err.expose = true;
throw err;注意:这些是用户级错误,且被标记为 err.expose,其意味着这些消息被准确描述为对客户端的响应,而并非使用在您不想泄露失败细节的场景中。
You may optionally pass a properties object which is merged into the error as-is, useful for decorating machine-friendly errors which are reported to the requester upstream.
ctx.throw(401, 'access_denied', { user: user });koa uses http-errors to create errors.
ctx.assert(value, [status], [msg], [properties])
Helper method to throw an error similar to .throw() when !value. Similar to node's assert() method.
ctx.assert(ctx.state.user, 401, 'User not found. Please login!');koa uses http-assert for assertions.
ctx.respond
为了避免使用 Koa 的内置响应处理功能,您可以直接赋值 ctx.repond = false;。如果您不想让 Koa 来帮助您处理 response,而是直接操作原生 res 对象,那么请使用这种方法。
注意: 这种方式是不被 Koa 支持的。其可能会破坏 Koa 中间件和 Koa 本身的一些功能。其只作为一种 hack 的方式,并只对那些想要在 Koa 方法和中间件中使用传统 fn(req, res) 方法的人来说会带来便利。
Request aliases
以下访问器和别名与 Request 等价:
ctx.headerctx.headersctx.methodctx.method=ctx.urlctx.url=ctx.originalUrlctx.originctx.hrefctx.pathctx.path=ctx.queryctx.query=ctx.querystringctx.querystring=ctx.hostctx.hostnamectx.freshctx.stalectx.socketctx.protocolctx.securectx.ipctx.ipsctx.subdomainsctx.is()ctx.accepts()ctx.acceptsEncodings()ctx.acceptsCharsets()ctx.acceptsLanguages()ctx.get()
Response aliases
以下访问器和别名与 Response 等价:
ctx.bodyctx.body=ctx.statusctx.status=ctx.messagectx.message=ctx.length=ctx.lengthctx.type=ctx.typectx.headerSentctx.redirect()ctx.attachment()ctx.set()ctx.append()ctx.remove()ctx.lastModified=ctx.etag=
请求 Request
Koa Request 对象是对 node 的 request 对象 的 进一步抽象和封装,提供了日常 HTTP 服务器开发中一些有用的功能。
API
request.header
请求头对象
request.headers
Request header object. Alias as request.header.
request.method
请求方法
request.method=
设置请求方法,在实现中间件时非常有用,比如 methodOverride()。
request.length
以数字的形式返回 request 的内容长度(Content-Length),或者返回 undefined。
request.url
获得请求url地址。
request.url=
设置请求地址,用于重写url地址时。
request.originalUrl
获取请求原始地址。
request.origin
Get origin of URL, include protocol and host.
ctx.request.origin
// => http://example.comrequest.href
Get full request URL, include protocol, host and url.
ctx.request.href
// => http://example.com/foo/bar?q=1request.path
获取请求路径名。
request.path=
设置请求路径名,并保留请求参数(就是url中?后面的部分)。
request.querystring
获取查询参数字符串(url中?后面的部分),不包含 ?。
request.querystring=
设置查询参数。
request.search
获取查询参数字符串,包含 ?。
request.search=
设置查询参数字符串。
request.host
获取 host (hostname:port)。 当 app.proxy 设置为 true 时,支持 X-Forwarded-Host。
request.hostname
获取 hostname。当 app.proxy 设置为 true 时,支持 X-Forwarded-Host。
request.type
获取请求 Content-Type,不包含像 "charset" 这样的参数。
var ct = ctx.request.type;
// => "image/png" request.charset
获取请求 charset,没有则返回 undefined:
ctx.request.charset
// => "utf-8" request.query
将查询参数字符串进行解析并以对象的形式返回,如果没有查询参数字字符串则返回一个空对象。
注意:该方法不支持嵌套解析。
比如 "color=blue&size=small":
{
color: 'blue',
size: 'small'
} request.query=
根据给定的对象设置查询参数字符串。
注意:该方法不支持嵌套对象。
ctx.query = { next: '/login' }; request.fresh
检查请求缓存是否 "fresh"(内容没有发生变化)。该方法用于在 If-None-Match / ETag, If-Modified-Since 和 Last-Modified 中进行缓存协调。当在 response headers 中设置一个或多个上述参数后,该方法应该被使用。
// freshness check requires status 20x or 304
ctx.status = 200;
ctx.set('ETag', '123');
// cache is ok
if (ctx.fresh) {
ctx.status = 304;
return;
}
// cache is stale
// fetch new data
ctx.body = yield db.find('something');request.stale
与 request.fresh 相反。
request.protocol
返回请求协议,"https" 或者 "http"。 当 app.proxy 设置为 true 时,支持 X-Forwarded-Host。
request.secure
简化版 this.protocol == "https",用来检查请求是否通过 TLS 发送。
request.ip
请求远程地址。 当 app.proxy 设置为 true 时,支持 X-Forwarded-Host。
request.ips
当 X-Forwarded-For 存在并且 app.proxy 有效,将会返回一个有序(从 upstream 到 downstream)ip 数组。 否则返回一个空数组。
request.subdomains
以数组形式返回子域名。
子域名是在host中逗号分隔的主域名前面的部分。默认情况下,应用的域名假设为host中最后两部分。其可通过设置 app.subdomainOffset 进行更改。
举例来说,如果域名是 "tobi.ferrets.example.com":
如果没有设置 app.subdomainOffset,其 subdomains 为 ["ferrets", "tobi"]。 如果设置 app.subdomainOffset 为3,其 subdomains 为 ["tobi"]。
request.is(types...)
检查请求所包含的 "Content-Type" 是否为给定的 type 值。 如果没有 request body,返回 undefined。 如果没有 content type,或者匹配失败,返回 false。 否则返回匹配的 content-type。
// With Content-Type: text/html; charset=utf-8
ctx.is('html'); // => 'html'
ctx.is('text/html'); // => 'text/html'
ctx.is('text/*', 'text/html'); // => 'text/html'
// When Content-Type is application/json
ctx.is('json', 'urlencoded'); // => 'json'
ctx.is('application/json'); // => 'application/json'
ctx.is('html', 'application/*'); // => 'application/json'
ctx.is('html'); // => false比如说您希望保证只有图片发送给指定路由:
if (ctx.is('image/*')) {
// process
} else {
ctx.throw(415, 'images only!');
}Content Negotiation
Koa request 对象包含 content negotiation 功能(由 accepts 和 negotiator 提供):
request.accepts(types)request.acceptsEncodings(types)request.acceptsCharsets(charsets)request.acceptsLanguages(langs)
如果没有提供 types,将会返回所有的可接受类型。
如果提供多种 types,将会返回最佳匹配类型。如果没有匹配上,则返回 false,您应该给客户端返回 406 "Not Acceptable"。
为了防止缺少 accept headers 而导致可以接受任意类型,将会返回第一种类型。因此,您提供的类型顺序非常重要。
request.accepts(types)
检查给定的类型 types(s) 是否可被接受,当为 true 时返回最佳匹配,否则返回 false。type 的值可以是一个或者多个 mime 类型字符串。 比如 "application/json" 扩展名为 "json",或者数组 ["json", "html", "text/plain"]。
// Accept: text/html
ctx.accepts('html');
// => "html"
// Accept: text/*, application/json
ctx.accepts('html');
// => "html"
ctx.accepts('text/html');
// => "text/html"
ctx.accepts('json', 'text');
// => "json"
ctx.accepts('application/json');
// => "application/json"
// Accept: text/*, application/json
ctx.accepts('image/png');
ctx.accepts('png');
// => false
// Accept: text/*;q=.5, application/json
ctx.accepts(['html', 'json']);
ctx.accepts('html', 'json');
// => "json"
// No Accept header
ctx.accepts('html', 'json');
// => "html"
ctx.accepts('json', 'html');
// => "json"ctx.accepts() 可以被调用多次,或者使用 switch:
switch (ctx.accepts('json', 'html', 'text')) {
case 'json': break;
case 'html': break;
case 'text': break;
default: ctx.throw(406, 'json, html, or text only');
}request.acceptsEncodings(encodings)
检查 encodings 是否可以被接受,当为 true 时返回最佳匹配,否则返回 false。 注意:您应该在 encodings 中包含 identity。
// Accept-Encoding: gzip
ctx.acceptsEncodings('gzip', 'deflate', 'identity');
// => "gzip"
ctx.acceptsEncodings(['gzip', 'deflate', 'identity']);
// => "gzip"当没有传递参数时,返回包含所有可接受的 encodings 的数组:
// Accept-Encoding: gzip, deflate
ctx.acceptsEncodings();
// => ["gzip", "deflate", "identity"]
注意:如果客户端直接发送 identity;q=0 时,identity encoding(表示no encoding) 可以不被接受。虽然这是一个边界情况,您仍然应该处理这种情况。
request.acceptsCharsets(charsets)
检查 charsets 是否可以被接受,如果为 true 则返回最佳匹配, 否则返回 false。
// Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5
ctx.acceptsCharsets('utf-8', 'utf-7');
// => "utf-8"
ctx.acceptsCharsets(['utf-7', 'utf-8']);
// => "utf-8"
当没有传递参数时, 返回包含所有可接受的 charsets 的数组:
// Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5
ctx.acceptsCharsets();
// => ["utf-8", "utf-7", "iso-8859-1"]
request.acceptsLanguages(langs)
检查 langs 是否可以被接受,如果为 true 则返回最佳匹配,否则返回 false。
// Accept-Language: en;q=0.8, es, pt
ctx.acceptsLanguages('es', 'en');
// => "es"
ctx.acceptsLanguages(['en', 'es']);
// => "es"
当没有传递参数时,返回包含所有可接受的 langs 的数组:
// Accept-Language: en;q=0.8, es, pt
ctx.acceptsLanguages();
// => ["es", "pt", "en"]
request.idempotent
检查请求是否为幂等(idempotent)。
request.socket
返回请求的socket。
request.get(field)
返回请求 header 中对应 field 的值。
响应 Response
Koa Response 对象是对 node 的 response 进一步抽象和封装,提供了日常 HTTP 服务器开发中一些有用的功能。
API
response.header
Response header object.
response.headers
Response header object. Alias as response.header.
response.socket
Request socket.
response.status
Get response status. By default, response.status is set to 404 unlike node's res.statusCode which defaults to 200.
response.status=
Set response status via numeric code:
- 100 "continue"
- 101 "switching protocols"
- 102 "processing"
- 200 "ok"
- 201 "created"
- 202 "accepted"
- 203 "non-authoritative information"
- 204 "no content"
- 205 "reset content"
- 206 "partial content"
- 207 "multi-status"
- 300 "multiple choices"
- 301 "moved permanently"
- 302 "moved temporarily"
- 303 "see other"
- 304 "not modified"
- 305 "use proxy"
- 307 "temporary redirect"
- 400 "bad request"
- 401 "unauthorized"
- 402 "payment required"
- 403 "forbidden"
- 404 "not found"
- 405 "method not allowed"
- 406 "not acceptable"
- 407 "proxy authentication required"
- 408 "request time-out"
- 409 "conflict"
- 410 "gone"
- 411 "length required"
- 412 "precondition failed"
- 413 "request entity too large"
- 414 "request-uri too large"
- 415 "unsupported media type"
- 416 "requested range not satisfiable"
- 417 "expectation failed"
- 418 "i'm a teapot"
- 422 "unprocessable entity"
- 423 "locked"
- 424 "failed dependency"
- 425 "unordered collection"
- 426 "upgrade required"
- 428 "precondition required"
- 429 "too many requests"
- 431 "request header fields too large"
- 500 "internal server error"
- 501 "not implemented"
- 502 "bad gateway"
- 503 "service unavailable"
- 504 "gateway time-out"
- 505 "http version not supported"
- 506 "variant also negotiates"
- 507 "insufficient storage"
- 509 "bandwidth limit exceeded"
- 510 "not extended"
- 511 "network authentication required"
NOTE: don't worry too much about memorizing these strings, if you have a typo an error will be thrown, displaying this list so you can make a correction.
response.message
Get response status message. By default, response.message is associated with response.status.
response.message=
Set response status message to the given value.
response.length=
Set response Content-Length to the given value.
response.length
Return response Content-Length as a number when present, or deduce from ctx.body when possible, or undefined.
response.body
Get response body.
response.body=
Set response body to one of the following:
- string written
- Buffer written
- Stream piped
- Object || Array json-stringified
- null no content response
If response.status has not been set, Koa will automatically set the status to 200or 204.
String
The Content-Type is defaulted to text/html or text/plain, both with a default charset of utf-8. The Content-Length field is also set.
Buffer
The Content-Type is defaulted to application/octet-stream, and Content-Length is also set.
Stream
The Content-Type is defaulted to application/octet-stream.
Whenever a stream is set as the response body, .onerror is automatically added as a listener to the error event to catch any errors. In addition, whenever the request is closed (even prematurely), the stream is destroyed. If you do not want these two features, do not set the stream as the body directly. For example, you may not want this when setting the body as an HTTP stream in a proxy as it would destroy the underlying connection.
See: https://github.com/koajs/koa/pull/612 for more information.
Here's an example of stream error handling without automatically destroying the stream:
const PassThrough = require('stream').PassThrough;
app.use(function * (next) {
ctx.body = someHTTPStream.on('error', ctx.onerror).pipe(PassThrough());
});Object
The Content-Type is defaulted to application/json. This includes plain objects { foo: 'bar' } and arrays ['foo', 'bar'].
response.get(field)
Get a response header field value with case-insensitive field.
const etag = ctx.get('ETag');response.set(field, value)
Set response header field to value:
ctx.set('Cache-Control', 'no-cache');response.append(field, value)
Append additional header field with value val.
ctx.append('Link', '<http://127.0.0.1/>');response.set(fields)
Set several response header fields with an object:
ctx.set({
'Etag': '1234',
'Last-Modified': date
});response.remove(field)
Remove header field.
response.type
Get response Content-Type void of parameters such as "charset".
const ct = ctx.type;
// => "image/png"response.type=
Set response Content-Type via mime string or file extension.
ctx.type = 'text/plain; charset=utf-8';
ctx.type = 'image/png';
ctx.type = '.png';
ctx.type = 'png';Note: when appropriate a charset is selected for you, for example response.type = 'html' will default to "utf-8", however when explicitly defined in full as response.type = 'text/html' no charset is assigned.
response.is(types...)
Very similar to ctx.request.is(). Check whether the response type is one of the supplied types. This is particularly useful for creating middleware that manipulate responses.
For example, this is a middleware that minifies all HTML responses except for streams.
const minify = require('html-minifier');
app.use(function * minifyHTML(next) {
yield next;
if (!ctx.response.is('html')) return;
let body = ctx.body;
if (!body || body.pipe) return;
if (Buffer.isBuffer(body)) body = body.toString();
ctx.body = minify(body);
});response.redirect(url, [alt])
Perform a [302] redirect to url.
The string "back" is special-cased to provide Referrer support, when Referrer is not present alt or "/" is used.
ctx.redirect('back');
ctx.redirect('back', '/index.html');
ctx.redirect('/login');
ctx.redirect('http://google.com');To alter the default status of 302, simply assign the status before or after this call. To alter the body, assign it after this call:
ctx.status = 301;
ctx.redirect('/cart');
ctx.body = 'Redirecting to shopping cart';response.attachment([filename])
Set Content-Disposition to "attachment" to signal the client to prompt for download. Optionally specify the filename of the download.
response.headerSent
Check if a response header has already been sent. Useful for seeing if the client may be notified on error.
response.lastModified
Return the Last-Modified header as a Date, if it exists.
response.lastModified=
Set the Last-Modified header as an appropriate UTC string. You can either set it as a Date or date string.
ctx.response.lastModified = new Date();response.etag=
Set the ETag of a response including the wrapped "s. Note that there is no corresponding response.etag getter.
ctx.response.etag = crypto.createHash('md5').update(ctx.body).digest('hex');response.vary(field)
Vary on field.
response.flushHeaders()
Flush any set headers, and begin the body.
相关资源 Links
Community links to discover third-party middleware for Koa, full runnable examples, thorough guides and more! If you have questions join us in IRC! 以下列出了更多第三方提供的 koa 中间件、完整实例、全面的帮助文档等。如果有问题,请加入我们的 IRC!
- GitHub repository
- Examples
- Middleware
- Wiki
- G+ Community
- Mailing list
- Guide
- FAQ
- #koajs on freenode
koa-router
koa路由中间件
- 类express风格的路由使用方式app.get,app.put,app.post等
- 命名的URL参数
- 使用url命名的路由
- Responds to OPTIONS requests with allowed methods.
- 支持405 Method Not Allowed 和 501 Not Implemented
- 多路由中间件
- 多种路由定义
- 嵌套的路由
- 支持 ES7 async/await
Migrating to 7 / Koa 2
- The API has changed to match the new promise-based middleware signature of koa 2. See thekoa 2.x readme for more information.
- Middleware is now always run in the order declared by .use() (or .get(), etc.), which matches Express 4 API.
API Reference
- koa-router
- Router ⏏
- new Router([opts])
- instance
- .get|put|post|patch|delete|del ⇒ Router
- .routes ⇒ function
- .use([path], middleware) ⇒ Router
- .prefix(prefix) ⇒ Router
- .allowedMethods([options]) ⇒ function
- .redirect(source, destination, code) ⇒ Router
- .route(name) ⇒ Layer | false
- .url(name, params) ⇒ String | Error
- .param(param, middleware) ⇒ Router
- static
- .url(path, params) ⇒ String
- Router ⏏
Router ⏏
new Router([opts])
Create a new router.
Basic usage:
var Koa = ;var Router = ; var app = ;var router = ; router; app ;Instance
router.get|put|post|patch|delete|del ⇒ Router
Create router.verb() methods, where verb is one of the HTTP verbs such as router.get()or router.post().
Match URL patterns to callback functions or controller actions using router.verb(), where verb is one of the HTTP verbs such as router.get() or router.post().
Additionaly, router.all() can be used to match against all methods.
router all'/users/:id' { // ... };When a route is matched, its path is available at ctx._matchedRoute and if named, the name is available at ctx._matchedRouteName
Route paths will be translated to regular expressions using path-to-regexp.
Query strings will not be considered when matching requests.
Named routes
路由有一个可选的名字,让你在开发阶段能够轻松的生成一个url和重命名一个url。
router; router;// => "/users/3" Multiple middleware
我们可以为我们的某个路由指定多个中间件回调函数,这些中间件会被一次调用。这些路由中间件之间的上下文是共享的。
router;Nested routers
Nesting routers is supported:
var forums = ;var posts = ; posts;posts;forums; // responds to "/forums/123/posts" and "/forums/123/posts/123" app;Router prefixes
路由在创建的时候可以指定一个前缀,这个前缀会被置于路由的最顶层,也就是说,这个路由的所有请求都是相对于这个前缀的。
var router = prefix: '/users'; router; // responds to "/users" router; // responds to "/users/:id" URL parameters
Named route parameters are captured and added to ctx.params.
router;The path-to-regexp module is used to convert paths to regular expressions.
Kind: instance property of Router
router.routes ⇒ function
返回一个函数,这个函数会根据请求的路径自动匹配路由。这是路由实例的一个属性。
Kind: instance property of Router
router.use([path], middleware) ⇒ Router
Use given middleware.
Middleware run in the order they are defined by .use(). They are invoked sequentially, requests start at the first middleware and work their way "down" the middleware stack.
Kind: instance method of Router
Example
// session middleware will run before authorize router ; // use middleware only with given path router; // or with an array of paths router; app;router.prefix(prefix) ⇒ Router
这是为路由设置前缀的第二种方式,为初始化后的路由设置前缀,也是路由实例的一个属性
两种方式:
//第一种:初始化的设置
const router = new Router({
prefix:'/user'
});
//第二种:初始化之后设置
const router = new Router();
router.prefix = '/user';Kind: instance method of Router
Example
routerprefix'/things/:thing_id'router.allowedMethods([options]) ⇒ function
Returns separate middleware for responding to OPTIONS requests with an Allow header containing the allowed methods, as well as responding with 405 Method Not Allowed and 501 Not Implemented as appropriate.
Kind: instance method of Router
Example
var Koa = ;var Router = ; var app = ;var router = ; app;app;Example with Boom
var Koa = ;var Router = ;var Boom = ; var app = ;var router = ; app;app;router.redirect(source, destination, code) ⇒ Router
Redirect source to destination URL with optional 30x status code.
Both source and destination can be route names.
router;This is equivalent to:
routerall'/login' { ctx; ctxstatus = 301;};Kind: instance method of Router
router.route(name) ⇒ Layer | false
Lookup route with given name.
Kind: instance method of Router
router.url(name, params) ⇒ String | Error
Generate URL for route. Takes a route name and map of named params.
Kind: instance method of Router
Example
router; router;// => "/users/3" router;// => "/users/3" routerrouter.param(param, middleware) ⇒ Router
Run middleware for named route parameters. Useful for auto-loading or validation.
Kind: instance method of Router
Example
router // /users/3 => {"id": 3, "name": "Alex"} // /users/3/friends => [{"id": 4, "name": "TJ"}] Router.url(path, params) ⇒ String
Generate URL from url pattern and given params.
Kind: static method of Router
Example
var url = Router;// => "/users/1" koa-bodyparser
Usage
var Koa = ;var bodyParser = ; var app = ;app; app;Options
enableTypes: parser will only parse when request type hits enableTypes, default is ['json', 'form'].
encode: requested encoding. Default is utf-8 by co-body.
formLimit: limit of the urlencoded body. If the body ends up being larger than this limit, a 413 error code is returned. Default is 56kb.
jsonLimit: limit of the json body. Default is 1mb.
textLimit: limit of the text body. Default is 1mb.
strict: when set to true, JSON parser will only accept arrays and objects. Default is true. See strict mode in co-body. In strict mode, ctx.request.body will always be an object(or array), this avoid lots of type judging. But text body will always return string type.
detectJSON: custom json request detect function. Default is null.
app;extendTypes: support extend types:
app;onerror: support custom error handle, if koa-bodyparser throw an error, you can customize the response like:
app;disableBodyParser: you can dynamic disable body parser by set ctx.disableBodyParser = true.
app;app;Raw Body
You can access raw request body by ctx.request.rawBody after koa-bodyparser when:
- koa-bodyparser parsed the request body.
- ctx.request.rawBody is not present before koa-bodyparser.
Nunjucks
A rich and powerful templating language for JavaScript.
类似于 Python 的模板引擎 jinja2
优点
- Rich Powerful language with block inheritance, autoescaping, macros, asynchronous control, and more. Heavily inspired by jinja2
- Fast & Lean High-performant. Small 8K gzipped runtime with precompiled templates in the browser
- Extensible Crazy extensible with custom filters and extensions
- Everywhere Available in node and all modern web browsers, with thorough precompilation options
{'% extends "base.html" %'}
{'% block header %'}
<h1>{'{ title }'}</h1>
{'% endblock %'}
{'% block content %'}
<ul>
{'% for name, item in items %'}
<li>{'{ name }'}: {'{ item }'}</li>
{'% endfor %'}
</ul>
{'% endblock %'}我们选择Nunjucks作为模板引擎。Nunjucks是Mozilla开发的一个纯JavaScript编写的模板引擎,既可以用在Node环境下,又可以运行在浏览器端。但是,主要还是运行在Node环境下,因为浏览器端有更好的模板解决方案,例如MVVM框架。
如果你使用过Python的模板引擎jinja2,那么使用Nunjucks就非常简单,两者的语法几乎是一模一样的,因为Nunjucks就是用JavaScript重新实现了jinjia2。
虽然模板引擎内部可能非常复杂,但是使用一个模板引擎是非常简单的,因为本质上我们只需要构造这样一个函数:
function render(view, model) { // TODO:... }
其中,view是模板的名称(又称为视图),因为可能存在多个模板,需要选择其中一个。model就是数据,在JavaScript中,它就是一个简单的Object。render函数返回一个字符串,就是模板的输出。
快速上手
Node 端使用
$ npm install nunjucks
下载后可直接 require('nunjucks') 使用
浏览器端使用
可直接使用 nunjucks.js (min),如果针对编译后的模板可使用 nunjucks-slim.js (min)。
你应该使用哪个文件
nunjucks.js 可用于动态加载模板,当模板变化时重新加载,也可以用于预编译后的模板。包含编译器,所以会比较大 (20K min/gzipped)。如果你刚接触 nunjucks 可使用这个文件,如果你不在意大小也可以在生产环境使用。
nunjucks-slim.js 只能用于预编译后的模板,只包含运行时的代码,所以比较小 (8K min/gzipped)。一般用于生产环境,如果你使用 grunt 或gulp任务自动预编译,也可以在开发环境使用。
直接用 script 引入文件:
<script src="nunjucks.js"></script>
或者可以作为一个 AMD 模块加载:
define(['nunjucks'], function(nunjucks) {
});
使用说明
这是最简单使用 nunjucks 的方式,首先设置配置项(如 autoescaping),然后渲染一个字符串:
nunjucks.configure({ autoescape: true });
nunjucks.renderString('Hello {'{ username }'}', { username: 'James' });
renderString 并不常用,而是使用 render 来直接渲染文件,这种方式支持继承(extends)和包含(include)模板。使用之前需要配置文件的路径:
nunjucks.configure('views', { autoescape: true });
nunjucks.render('index.html', { foo: 'bar' });
在 node 端,'views' 为相对于当前工作目录 (working directory) 的路径。在浏览器端则为一个相对的 url,最好指定为绝对路径 (如 '/views')。
如果使用 express 可直接传入 configure:
var app = express();
nunjucks.configure('views', {
autoescape: true,
express: app
});
app.get('/', function(req, res) {
res.render('index.html');
});
上面的 API 适用于 node 端和浏览器端 (express 只适用于 node 端),在 node 端 nunjucks 从文件系统加载模板,在浏览器端通过 http 加载模板。
如果你在浏览器上使用编译后的模板的话,你不需要额外做其他的事情系统也能够理解它们。这使得我们可以轻松地在开发环境和生产环境上使用同一份代码,并在生产环境上只使用已经编译过的模板。
更多信息
API
- Simple API
- Environment
- Template
- Loader
- Browser Usage
- Precompiling
- Asynchronous Support
- Autoescaping
- Customizing Syntax
- Custom Filters
- Custom Tags
Nunjucks 的 API 包括渲染模板,添加过滤器和扩展,自定义模板加载器等等。
注意: nunjucks并不是在沙盒中运行的,所以使用用户定义的模板可能存在风险。这可能导致的风险有:在服务器上运行时敏感数据被窃取,或是在客户端运行时遭遇跨站脚本攻击(详情请查看这里)。
Simple API
如果你不需要深度定制,可以直接使用初级(higher-level) api 来加载和渲染模板。
render
渲染模式时需要两个参数,模板名 name 和数据 context。如果 callback 存在,当渲染完成后会被调用,第一个参数是错误,第二个为返回的结果;如果不存在,render 方法会直接返回结果,错误时会抛错。更多查看异步的支持。
var res = nunjucks.render('foo.html');
var res = nunjucks.render('foo.html', { username: 'James' });
nunjucks.render('async.html', function(err, res) {
});
renderString
与 render 类似,只是渲染一个字符串而不是渲染加载的模板。
var res = nunjucks.renderString('Hello {'{ username }'}', { username: 'James' });
compile
将给定的字符串编译成可重复使用的nunjucks模板对象。
var template = nunjucks.compile('Hello {'{ username }'}');
template.render({ username: 'James' });
configure
传入 path 指定存放模板的目录,opts 可让某些功能开启或关闭,这两个变量都是可选的。path 的默认值为当前的工作目录,opts 提供以下功能:
- autoescape (默认值: true) 控制输出是否被转义,查看 Autoescaping
- throwOnUndefined (default: false) 当输出为 null 或 undefined 会抛出异常
- trimBlocks (default: false) 自动去除 block/tag 后面的换行符
- lstripBlocks (default: false) 自动去除 block/tag 签名的空格
- watch (默认值: false) 当模板变化时重新加载。使用前请确保已安装可选依赖 chokidar。
- noCache (default: false) 不使用缓存,每次都重新编译
- web 浏览器模块的配置项
- useCache (default: false) 是否使用缓存,否则会重新请求下载模板
- async (default: false) 是否使用 ajax 异步下载模板
- express 传入 express 实例初始化模板设置
- tags: (默认值: see nunjucks syntax) 定义模板语法,查看 Customizing Syntax
configure 返回一个 Environment 实例, 他提供了简单的 api 添加过滤器 (filters) 和扩展 (extensions),可在 Environment 查看更多的使用方法。
注意: 简单的API (比如说上面的nunjucks.render) 通常会使用最近一次调用nunjucks.configure时的配置。由于这种做法是隐性的,它可能会渲染出意料之外的结果,所以在大多数情况下我们不推荐使用这类简单的API(特别是用到configure的情况);我们推荐使用var env = nunjucks.configure(...)创建一个独立的环境,并调用env.render(...)进行渲染。
nunjucks.configure('views');
// 在浏览器端最好使用绝对地址
nunjucks.configure('/views');
nunjucks.configure({ autoescape: true });
nunjucks.configure('views', {
autoescape: true,
express: app,
watch: true
});
var env = nunjucks.configure('views');
// do stuff with env
installJinjaCompat
这个方法为了与 Jinja 更好的兼容,增加了一些适配 Python 的 API。但是 nunjucks 不是为了完全兼容 Jinja/Pyhton,这只为了帮助使用者查看。
增加了 True 和 False,与 js 的 true 和 false 相对应。并增加 Array 和 Object 使之适配 Python 风格的。查看源码能看到所有功能。
就是这么简单,如果希望自己定义模板加载等更多的个性化设置,那么可以继续往下看。
Environment
Environment 类用来管理模板,使用他可以加载模板,模板之间可以继承和包含,上面提到的 api 都是调用了 Environment 的 api。
你可以根据需要来定制,比如定制模板加载。
constructor
实例化 Environment 时传入两个参数,一组 loaders 和配置项 opts。如果 loaders 不存在,则默认从当前目录或地址加载。loaders 可为一个或多个,如果传入一个数组,nunjucks 会按顺序查找直到找到模板。更多查看 Loader
opts 的配置有 autoescape、throwOnUndefined、trimBlocks 和 lstripBlocks,在 configure 查看具体配置(express 和 watch 配置在这里不适用,而是在 env.express 进行配置)。
在 node 端使用 FileSystemLoader 加载模板,浏览器端则使用 WebLoader 通过 http 加载(或使用编译后的模板)。如果你使用了 configure 的 api,nunjucks 会根据平台(node 或浏览器)自动选择对应的 loader。查看更多 Loader。
// the FileSystemLoader is available if in node
var env = new nunjucks.Environment(new nunjucks.FileSystemLoader('views'));
var env = new nunjucks.Environment(new nunjucks.FileSystemLoader('views'),
{ autoescape: false });
var env = new nunjucks.Environment([new nunjucks.FileSystemLoader('views'),
new MyCustomLoader()]);
// the WebLoader is available if in the browser
var env = new nunjucks.Environment(new nunjucks.WebLoader('/views'));
render
渲染名为 name 的模板,使用 context 作为数据,如果 callback 存在,在完成时会调用,回调有两个参数:错误和结果( 查看 asynchronous support)。如果 callback 不存在则直接回返结果。
var res = nunjucks.render('foo.html');
var res = nunjucks.render('foo.html', { username: 'James' });
nunjucks.render('async.html', function(err, res) {
});
renderString
和 render 相同,只是渲染一个字符串而不是加载的模块。
var res = nunjucks.renderString('Hello {'{ username }'}', { username: 'James' });
addFilter
添加名为 name 的自定义过滤器,func 为调用的函数,如果过滤器需要异步的,async 应该为 true (查看 asynchronous support)。查看 Custom Filters。
getFilter
获取过滤器,传入名字返回一个函数。
addExtension
添加一个名为 name 的扩展,ext 为一个对象,并存在几个指定的方法供系统调用,查看 Custom Tags。
removeExtension
删除之前添加的扩展 name。
getExtension
获取扩展,传入名字返回一个函数。
hasExtension
如果 name 扩展已经被添加,那返回 true。
addGlobal
添加一个全局变量,可以在所有模板使用。注意:这个会覆盖已有的 name变量。
getGlobal
返回一个名为 name 的全局变量。
getTemplate
获取一个名为 name 的模板。如果 eagerCompile 为 true,模板会立即编译而不是在渲染的时候再编译。如果 callback 存在会被调用,参数为错误和模板,否则会直接返回。如果使用异步加载器,则必须使用异步的 api,内置的加载器不需要。查看 asynchronous support 和 loaders。
var tmpl = env.getTemplate('page.html');
var tmpl = env.getTemplate('page.html', true);
env.getTemplate('from-async-loader.html', function(err, tmpl) {
});
express
使用 nunjucks 作为 express 的模板引擎,调用后可正常使用 express。你也可以调用 configure,将 app 作为 express 参数传入。
var app = express();
env.express(app);
app.get('/', function(req, res) {
res.render('index.html');
});
opts.autoescape
你可以通过这个配置控制是否全局开启模板转义,这对于创建高级过滤(如 html 操作)非常有用。 正常情况你可以返回 SafeString,输出保持和输入一致不做任何处理,但这只在很少场景下有用。
Template
Template 是一个模板编译后的对象,然后进行渲染。通常情况下,Environment 已经帮你处理了,但你也可以自己进行处理。 如果使用 Template 渲染模板时未指定 Environment,那么这个模板不支持包含 (include) 和继承 (inherit) 其他模板。
constructor
实例化 Template 时需要四个参数,src 为模板的字符串,Environment的实例 env(可选)用来加载其他模板,path 为一个路径,在调试中使用,如果 eagerCompile 为 true,模板会立即编译而不是在渲染的时候再编译。
var tmpl = new nunjucks.Template('Hello {'{ username }'}');
tmpl.render({ username: "James" }); // -> "Hello James"
render
渲染模板,context 为数据,如果 callback 存在会在渲染完成后调用,参数为错误和结果 (查看 asynchronous support),否则直接返回。
Loader
加载器是一个对象,从资源(如文件系统或网络)中加载模板,以下为两个内置的加载器。 A loader is an object that takes a template name and loads it from a source, such as the filesystem or network. The following two builtin loaders exist, each for different contexts.
FileSystemLoader
只在 node 端可用,他可从文件系统中加载模板,searchPaths 为查找模板的路径,可以是一个也可以是多个,默认为当前的工作目录。
opt 为一个对象,包含如下属性:
- watch - 如果为 true,当文件系统上的模板变化了,系统会自动更新他。使用前请确保已安装可选依赖 chokidar。
- noCache - 如果为 true,不使用缓存,模板每次都会重新编译。
// Loads templates from the "views" folder
var env = new nunjucks.Environment(new nunjucks.FileSystemLoader('views'));
WebLoader
只在浏览器端可用,通过 baseURL(必须为同域)加载模板,默认为当前相对目录。
opt 为一个对象,包含如下属性:
- useCache 如果为 true,模板将会永远缓存,你将看不到他们更新。缓存默认关闭,因为这样就无法监听变化并更新缓存。 记住,你应该在生产环境预编译模板。
- async 如果为 true,模板会异步请求,而非同步,同时你必须使用异步 render API(调用 render 时传入一个 callback)。
他还能加载预编译后的模板,自动使用这些模板而不是通过 http 获取,在生产环境应该使用预编译。查看 Precompiling Templates。
// Load templates from /views
var env = new nunjucks.Environment(new nunjucks.WebLoader('/views'))
Writing a Loader
你可以自己写一个更复杂一点的加载器(如从数据库加载),只需建一个对象,添加一个 getSource(name) 的方法,name 为模板名。
function MyLoader(opts) {
// configuration
}
MyLoader.prototype.getSource = function(name) {
// load the template
// return an object with:
// - src: String. The template source.
// - path: String. Path to template.
// - noCache: Bool. Don't cache the template (optional).
}
如果你希望跟踪模板的更新,并当有更新时清除缓存,这样就有一点复杂了。但你可以继承 Loader 类,可以通过 emit 方法触发事件。
var MyLoader = nunjucks.Loader.extend({
init: function() {
// setup a process which watches templates here
// and call `this.emit('update', name)` when a template
// is changed
}
getSource: function(name) {
// load the template
}
});
Asynchronous
这是最后一部分:异步加载器。到现在为止,所有的加载器都是同步,getSource 立即返回资源。这个的好处是用户不必强制使用异步 api,也不用担心异步模板的边缘问题。但是,你可以希望从数据库加载
只需在 load 中添加 async: true 属性就可支持异步调用
var MyLoader = nunjucks.Loader.extend({
async: true,
getSource: function(name, callback) {
// load the template
// ...
callback(err, res);
}
});
记住现在必须使用异步 api,查看 asynchronous support。
注意: 如果使用了异步加载器,你将不能在 for 循环中加载模块,但可以使用 asyncEach 替换之。asyncEach 和 for 相同,只是在异步的时候使用。更多查看 Be Careful!。
Browser Usage
在浏览器端使用 nunjucks 需要考虑更多,因为需要非常关注加载和编译时间。在服务端,模板一次编译后就缓存在内存中,就不用担心了。在浏览器端,你甚至不想编译他,因为会降低渲染的速度。
解决方案是将模板预编译成 javascript,和普通的 js 一样加载。
可能你想在开发时动态的加载模板,这样你可以在文件变化的时候马上看到而不需要预编译。Nunjucks 已经帮你适配了你想要的工作流。
有一点必须遵守:在生产环境一定要预编译模板。为什么?不仅因为在页面加载时编译模板速度很慢,而且是同步加载模板的,会阻塞整个页面。这很慢,因为 nunjucks 模板不是异步的。
Recommended Setups
在客户端,有两种最常用的方式来初始设置 nunjucks。注意这是两个不同的文件,其中一个包括编译器 nunjucks.js,另一个不包括 nunjucks-slim.js。查看 Getting Started 区分两者。
查看 Precompiling 了解预编译。
Setup #1: only precompile in production
这个方法可以让你在开发环境可以动态加载模板(可以马上看到变化),在生产环境使用预编译的模板。
- 使用 script 或模块加载器加载 nunjucks.js。
- 渲染模板 (example)!
- 当发布到生产环境时,When pushing to production, 将模板预编译 成 js 文件。
在生产环境中,你可以使用 nunjucks-slim.js 代替 nunjucks.js 进行优化,因为你使用了预编译的模板。nunjucks-slim.js 只有 8K 而不是 20K,因为不包括编译器。 但是这使初始设置复杂化了,因为在开发和生产环境需要不同的 js 文件,是否值得完全在你如何使用。
Setup #2: always precompile
这个方法是在开发和生产环境都使用预编译的模板,这样可以简化初始设置。但是在开发时,你需要一些工具来自动预编译,而不是手动编译。
- 开发时,使用 grunt或gulp 监听文件目录,当文件变化后自动编译成 js 文件。
- 使用 script 或模块加载器加载 nunjucks-slim.js 和你编译的 js 文件(如 templates.js)。
- 渲染模板 (example)!
使用这个方法,开发和生产环境无区别,只需提交 templates.js 并部署到生产环境。
Precompiling
使用 nunjucks-precompile 脚本来预编译模板,可传入一个目录或一个文件,他将把所有的模板生成 javascript。
// Precompiling a whole directory
$ nunjucks-precompile views > templates.js
// Precompiling individual templates
$ nunjucks-precompile views/base.html >> templates.js
$ nunjucks-precompile views/index.html >> templates.js
$ nunjucks-precompile views/about.html >> templates.js
你只需要在页面上加载 templates.js,系统会自动使用预编译的模板,没有改变的必要。
这个脚本还有很多可选项,直接调用 nunjucks-precompile 可以看到更多信息。注意所有的异步过滤器需要当参数传入,因为编译时需要他们,你可以使用 -a 参数来传入(如 -a foo,bar,baz)。如果只使用同步过滤器则不需要处理。
这个脚本无法指定扩展,所以你需要使用如下的预编译 api。
API
如果你希望通过代码来预编译模板,nunjucks 也提供了 api,特别是在使用扩展和异步过滤器的时候需要使用这些 api。可以将 Environment 的实例传给预编译器,其中将包括扩展和过滤器。你需要在客户端和服务器使用同一个 Environment 对象保证同步。
precompile
传入 path 预编译一个文件或目录,opts 为如下的一些配置:
- name: 模板的名字,当编译一个字符串的时候需要,如果是一个文件则是可选的(默认为 path),如果是目录名字会自动生成。
- asFunction: 生成一个函数
- force: 如果出错还继续编译
- env: Environment 的实例
- include: 包括额外的文件和文件夹 (folders are auto-included, files are auto-excluded)
- exclude: 排除额外的文件和文件夹 (folders are auto-included, files are auto-excluded)
- wrapper: function(templates, opts) 自定义预编译模板的输出格式。这个函数必须返回一个字符串
- templates: 由对象组成的,带有下列属性的数组:
- name: 模板名称
- template: 编译后的模板所生成的,运行在javascript上的字符串源码
- opts: 上面所有配置选项所组成的对象
- templates: 由对象组成的,带有下列属性的数组:
var env = new nunjucks.Environment();
// extensions must be known at compile-time
env.addExtension('MyExtension', new MyExtension());
// async filters must be known at compile-time
env.addFilter('asyncFilter', function(val, cb) {
// do something
}, true);
nunjucks.precompile('/dir/to/views', { env: env });
precompileString
和 precompile 相同,只是编译字符串。
Asynchronous Support
如果你对异步渲染感兴趣才需要看这节,并没有性能上的优势,只是支持异步的过滤器和扩展,如果你不关注异步,那么应该使用同步 api,如 var res = env.render('foo.html');。你不必每次都写 callback,这就是为什么在所有的渲染函数中是一个可选项。
nunjucks 1.0 会提供一种异步渲染模板的方式,这意味着自定义的过滤器和扩展可以做些类似从数据库获取内容的操作,模板渲染会等待直到调用回调。
模板加载器也可以异步,可使你从数据库或其他地方加载模板。查看 Writing a Loader。如果你在使用一个异步的模板加载,你需要使用异步的 api。内置的加载器是同步的,但并没有性能问题,因为文件系统是可以缓存的,而浏览器端会将模板编译成 js。
如果你使用了异步的,那么你需要使用异步的 api:
nunjucks.render('foo.html', function(err, res) {
// check err and handle result
});
了解更多异步相关的查看 filters, extensions 和 loaders.
Be Careful!
Nunjucks 默认是同步的,因此你需要按照如下规则写异步模板:
- 总是使用异步 api,调用 render 方法时应该有回调。
- 在编译时需要提供异步过滤器和扩展,所以在预编译时(查看Precompiling)需要指定。
- 如果你使用一个自定义的异步加载器,你不能在 for 中使用 include 模板,因为在 for 循环中会立即执行。而你需要使用 asyncEach 来循环,这和 for 的功能时相同的,但只用于异步场景。
Autoescaping
在默认情况下,nunjuck 渲染时会按原样输出,如果开启了自动转义 (autoescaping),nunjuck 会转义所有的输出,为了安全建议一直开启。
自动转义在 nunjucks 中非常简单,你只需将 autoescape 为 true 传入 Environment 对象。
var env = nunjucks.configure('/path/to/templates', { autoescape: true });
Customizing Syntax
如果你希望使用其他的 token 而不是 {'{,其中包括变量、区块和注释,你可以使用 tags 来定义不同的 token:
var env = nunjucks.configure('/path/to/templates', {
tags: {
blockStart: '<%',
blockEnd: '%>',
variableStart: '<$',
variableEnd: '$>',
commentStart: '<#',
commentEnd: '#>'
}
});使用这个 env,模板如下所示:
<ul>
<% for item in items %>
<li><$ item $></li>
<% endfor %>
</ul>
Custom Filters
使用 Environment 的 addFilter 方法添加一个自定义的过滤器,过滤器时一个函数,第一个参数为目标元素,剩下的参数为传入过滤器的参数。
var nunjucks = require('nunjucks');
var env = new nunjucks.Environment();
env.addFilter('shorten', function(str, count) {
return str.slice(0, count || 5);
});
添加了一个 shorten 的过滤器,返回前 count 位数的字符,count 默认为 5,如下为如何使用:
A message for you: {'{ message|shorten }'}
A message for you: {'{ message|shorten(20) }'}
Keyword/Default Arguments
在模板中说道,nunjucks 支持关键字参数,你可以在 filter 中使用他。
所有的关键字参数会以最后一个参数传入,以下为使用了关键字参数的 foo过滤器:
env.addFilter('foo', function(num, x, y, kwargs) {
return num + (kwargs.bar || 10);
})
模板可如下使用:
{'{ 5 | foo(1, 2) }'} -> 15
{'{ 5 | foo(1, 2, bar=3) }'} -> 8
你必须在关键字参数之前传入所有的位置参数 (foo(1) 是有效的,而 foo(1, bar=10) 不是),你不能使用将一个位置参数当作关键字参数来用 (如 foo(1, y=1))。
Asynchronous
异步过滤器接受一个回调继续渲染,调用 addFilter 时需传入第三个参数 true。
var env = nunjucks.configure('views');
env.addFilter('lookup', function(name, callback) {
db.getItem(name, callback);
}, true);
env.render('{'{ item|lookup }'}', function(err, res) {
// do something with res
});
回调需要两个参数 callback(err, res),err 可以为 null。
注意:当预编译时,你必须指定所有的异步过滤器,查看 Precompiling。
Custom Tags
你可以添加更多的自定义扩展,nunjucks 提供了 parser api 可以对模板做更多的事。
注意:当预编译时,你必须在编译时添加这些扩展,你应该使用 precompiling API (或者 grunt 或gulp任务),而不是预编译脚本。你需要创建一个 Environment 实例,添加扩展,传到预编译器中。
一个扩展至少有两个字段 tags 和 parse,扩展注册一个标签名,如果运行到这个标签则调用 parse。
tags 为这个扩展支持的一组标签名。parse 为一个函数,当编译时会解析模板。除此之外,还有一个特殊的节点名为 CallExtension,在运行时你可以调用本扩展的其他方法,下面会详细说明。
因为你需要直接使用 parse api,并且需要手动分析初 AST,所以有一点麻烦。如果你希望做一些复杂的扩展这是必须的。所以介绍一下你会用到的方法:
parseSignature([throwErrors], [noParens]) - 解析标签的参数。默认情况下,解析器会从括号左边解析到括号右边。但是自定义标签不应该时括号,所以将第二个参数设为 true 告诉解析器解析参数直到标签关闭。参数之间应该用逗号分隔,如 {'% mytag foo, bar, baz=10 %'}。
parseUntilBlocks(names) - 解析内容直到下一个名为 names 的标签,非常有用解析标签之间的内容。
parser API 还需要更多的文档,但现在对照上面的文档和下面的例子,你还可以看下源码。
最常用使用的是在运行时解析标签间的内容,就像过滤器一样,但是更灵活,因为不只是局限在一个表达式中。通常情况下你会解析模板,然后将内容传入回调。你可以使用 CallExtension,需要传扩展的实例,方法名,解析的参数和内容(使用 parseUntilBlocks 解析的)。
例如,下面实现了从远程获取内容并插入的扩展:
function RemoteExtension() {
this.tags = ['remote'];
this.parse = function(parser, nodes, lexer) {
// get the tag token
var tok = parser.nextToken();
// parse the args and move after the block end. passing true
// as the second arg is required if there are no parentheses
var args = parser.parseSignature(null, true);
parser.advanceAfterBlockEnd(tok.value);
// parse the body and possibly the error block, which is optional
var body = parser.parseUntilBlocks('error', 'endtruncate');
var errorBody = null;
if(parser.skipSymbol('error')) {
parser.skip(lexer.TOKEN_BLOCK_END);
errorBody = parser.parseUntilBlocks('endremote');
}
parser.advanceAfterBlockEnd();
// See above for notes about CallExtension
return new nodes.CallExtension(this, 'run', args, [body, errorBody]);
};
this.run = function(context, url, body, errorBody) {
var id = 'el' + Math.floor(Math.random() * 10000);
var ret = new nunjucks.runtime.SafeString('<div id="' + id + '">' + body() + '</div>');
var ajax = new XMLHttpRequest();
ajax.onreadystatechange = function() {
if(ajax.readyState == 4) {
if(ajax.status == 200) {
document.getElementById(id).innerHTML = ajax.responseText;
}
else {
document.getElementById(id).innerHTML = errorBody();
}
}
};
ajax.open('GET', url, true);
ajax.send();
return ret;
};
}
env.addExtension('RemoteExtension', new RemoteExtension());
模板可以这么写:
{'% remote "/stuff" %'}
This content will be replaced with the content from /stuff
{'% error %'}
There was an error fetching /stuff
{'% endremote %'}
Asynchronous
如果是异步的可以使用 CallExtensionAsync,在运行时扩展有一个回调作为最后一个参数,模板渲染会等待回调返回。
上面例子中的 run 如下使用
this.run = function(context, url, body, errorBody, callback) {
// do async stuff and then call callback(err, res)
};Templates
这里包括 Nunjuck 所有可用的功能。
Nunjucks 是 jinja2 的 javascript 的实现,所以如果此文档有什么缺失,你可以直接查看 jinja2 的文档,不过两者之间还存在一些差异。
文件扩展名
虽然你可以用任意扩展名来命名你的Nunjucks模版或文件,但Nunjucks社区还是推荐使用.njk。
如果你在给Nunjucks开发工具或是编辑器上的语法插件时,请记得使用.njk扩展名。
变量
变量会从模板上下文获取,如果你想显示一个变量可以:
{'{ username }'}
会从上下文查找 username 然后显示,可以像 javascript 一样获取变量的属性 (可使用点操作符或者中括号操作符):
{'{ foo.bar }'}
{'{ foo["bar"] }'}
如果变量的值为 undefined 或 null 将不显示,引用到 undefined 或 null 对象也是如此 (如 foo 为 undefined,{'{ foo }'}, {'{ foo.bar }'}, {'{ foo.bar.baz }'} 也不显示)。
过滤器
过滤器是一些可以执行变量的函数,通过管道操作符 (|) 调用,并可接受参数。
{'{ foo | title }'}
{'{ foo | join(",") }'}
{'{ foo | replace("foo", "bar") | capitalize }'}
第三个例子展示了链式过滤器,最终会显示 "Bar",第一个过滤器将 "foo" 替换成 "bar",第二个过滤器将首字母大写。
Nunjucks 提供了一些内置的过滤器,你也可以自定义过滤器。
模板继承
模板继承可以达到模板复用的效果,当写一个模板的时候可以定义 "blocks",子模板可以覆盖他,同时支持多层继承。
如果有一个叫做 parent.html 的模板,如下所示:
{'% block header %'}
This is the default content
{'% endblock %'}
<section class="left">
{'% block left %'}{'% endblock %'}
</section>
<section class="right">
{'% block right %'}
This is more content
{'% endblock %'}
</section>然后再写一个模板继承他
{'% extends "parent.html" %'}
{'% block left %'}
This is the left side!
{'% endblock %'}
{'% block right %'}
This is the right side!
{'% endblock %'}以下为渲染结果
This is the default content
<section class="left">
This is the left side!
</section>
<section class="right">
This is the right side!
</section>
你可以将继承的模板设为一个变量,这样就可以动态指定继承的模板。这个变量既可以是个指向模板文件的字符串,也可以是个模板编译后所生成的对象(需要添加上下文环境)。因此你可以通过设置上下文变量,从而在渲染时动态地改变所要继承的模板。
{'% extends parentTemplate %'}
继承功能使用了 extends 和 block 标签,jinja2 文档中有更细节的描述。
super
你可以通过调用super从而将父级区块中的内容渲染到子区块中。如果在前面的例子中你的子模板是这样的:
{'% block right %'}
{'{ super() }'}
Right side!
{'% endblock %'}
这个区块的渲染结果将是:
This is more content
Right side!
标签
标签是一些特殊的区块,它们可以对模板执行一些操作。Nunjucks 包含一些内置的标签,你也可以自定义。
if
if 为分支语句,与 javascript 中的 if 类似。
{'% if variable %'}
It is true
{'% endif %'}
如果 variable 定义了并且为 true (译者注:这里并非布尔值,和 javascript 的处理是一样的) 则会显示 "It is true",否则什么也不显示。
{'% if hungry %'}
I am hungry
{'% elif tired %'}
I am tired
{'% else %'}
I am good!
{'% endif %'}
在内联表达式(inline expression)中也可以使用 if。
for
for 可以遍历数组 (arrays) 和对象 (dictionaries)。
如果你使用的自定义模板加载器为异步的可查看 asyncEach
var items = [{ title: "foo", id: 1 }, { title: "bar", id: 2}];<h1>Posts</h1>
<ul>
{'% for item in items %'}
<li>{'{ item.title }'}</li>
{'% else %'}
<li>This would display if the 'item' collection were empty</li>
{'% endfor %'}
</ul>
上面的示例通过使用items数组中的每一项的title属性显示了所有文章的标题。如果items数组是空数组的话则会渲染else语句中的内容。
你还可以遍历对象:
var food = {
'ketchup': '5 tbsp',
'mustard': '1 tbsp',
'pickle': '0 tbsp'
};{'% for ingredient, amount in food %'}
Use {'{ amount }'} of {'{ ingredient }'}
{'% endfor %'}
dictsort 过滤器可将对象排序 (new in 0.1.8)
除此之外,Nunjucks 会将数组解开,数组内的值对应到变量 (new in 0.1.8)
var points = [[0, 1, 2], [5, 6, 7], [12, 13, 14]];{'% for x, y, z in points %'}
Point: {'{ x }'}, {'{ y }'}, {'{ z }'}
{'% endfor %'}
在循环中可获取一些特殊的变量
- loop.index: 当前循环数 (1 indexed)
- loop.index0: 当前循环数 (0 indexed)
- loop.revindex: 当前循环数,从后往前 (1 indexed)
- loop.revindex0: 当前循环数,从后往前 (0 based)
- loop.first: 是否第一个
- loop.last: 是否最后一个
- loop.length: 总数
asyncEach
这个是适用于异步模板,请读文档。
asyncEach 为 for 的异步版本,只有当使用自定义异步模板加载器的时候才使用,否则请不要使用。异步过滤器和扩展也需要他。如果你在循环中使用了异步过滤器的话,Nunjucks就会在内部自动将循环转换成 asyncEach。
asyncEach 和 for 的使用方式一致,但他支持循环的异步控制。将两者区分的原因是性能,大部分人使用同步模板,将 for 转换成原生的 for 语句会快很多。
编译时 nunjuck 不用关心模板是如何加载的,所以无法决定 include 是同步或异步。这也是为什么Nunjucks无法自动将普通的循环语句转换成异步循环语句的原因,所以如果你要使用异步模板加载器的话,就需要使用 asyncEach。
// If you are using a custom loader that is async, you need asyncEach
var env = new nunjucks.Environment(AsyncLoaderFromDatabase, opts);<h1>Posts</h1>
<ul>
{'% asyncEach item in items %'}
{'% include "item-template.html" %'}
{'% endeach %'}
</ul>
asyncAll
这个是适用于异步模板,请读文档。
asyncAll 和 asyncEach 类似,但 asyncAll 会并行的执行,并且每项的顺序仍然会保留。除非使用异步的过滤器、扩展或加载器,否则不要使用。
如果你写了一个 lookup 的过滤器用来从数据库获取一些文本,使用 asyncAll 可以并行渲染。
<h1>Posts</h1>
<ul>
{'% asyncAll item in items %'}
<li>{'{ item.id | lookup }'}</li>
{'% endall %'}
</ul>
如果 lookup 是一个异步的过滤器,那么可能会比较慢(如从磁盘获取些数据)。asyncAll 会减少执行的时间,他会并行执行所有的异步操作,当所有的操作完成后才会继续渲染页面。
macro
宏 (macro) 可以定义可复用的内容,类似与编程语言中的函数,看下面的示例:
{'% macro field(name, value='', type='text') %'}
<div class="field">
<input type="{'{ type }'}" name="{'{ name }'}"
value="{'{ value | escape }'}" />
</div>
{'% endmacro %'}
现在 field 可以当作函数一样使用了:
{'{ field('user') }'}
{'{ field('pass', type='password') }'}
支持关键字参数,通过链接查看具体使用方式。
还可以从其他模板 import 宏,可以使宏在整个项目中复用。
重要:如果你使用异步 API,请注意你 不能 在宏中做任何异步的操作,因为宏只是像函数一样被简单地调用。将来我们可能会提供一种异步的宏调用方式,但现在这么使用是不被支持的。
set
set 可以设置和修改变量。
{'{ username }'}
{'% set username = "joe" %'}
{'{ username }'}
如果 username 初始化的时候为 "james', 最终将显示 "james joe"。
可以设置新的变量,并一起赋值。
{'% set x, y, z = 5 %'}
如果在顶级作用域使用 set,将会改变全局的上下文中的值。如果只在某个作用域 (像是include或是macro) 中使用,则只会影响该作用域。
同样地,你也可以使用区块赋值将一个区块的内容储存在一个变量中。
它的语法和标准的set语法相似,只不过你不需要用=。区块中从头到{'% endset %'}之间的内容都会被捕获,并作为值来使用。
在某些情境下,你可以用这种语法来替代宏:
{'% set standardModal %'}
{'% include 'standardModalData.html' %'}
{'% endset %'}
<div class="js-modal" data-modal="{'{standardModal | e}'}">
extends
extends 用来指定模板继承,被指定的模板为父级模板,查看模板继承。
{'% extends "base.html" %'}
你可以将继承的模板设为一个变量,这样就可以动态指定继承的模板。这个变量既可以是个指向模板文件的字符串,也可以是个模板编译后所生成的对象(需要添加上下文环境)。因此你可以通过设置上下文变量,从而在渲染时动态地改变所要继承的模板。
{'% extends parentTemplate %'}
extends也可以接受任意表达式,只要它最终返回一个字符串或是模板所编译成的对象:
{'% extends name + ".html" %'}`.
block
区块(block) 定义了模板片段并标识一个名字,在模板继承中使用。父级模板可指定一个区块,子模板覆盖这个区块,查看模板继承。
{'% block css %'}
<link rel="stylesheet" href="app.css" />
{'% endblock %'}
可以在循环中定义区块
{'% for item in items %'}
{'% block item %'}{'{ item }'}{'% endblock %'}
{'% endfor %'}
子模板可以覆盖 item 区块并改变里面的内容。
{'% extends "item.html" %'}
{'% block item %'}
The name of the item is: {'{ item.name }'}
{'% endblock %'}
在区块中,你可以调用特殊的super函数。它会渲染父级区块中的内容。具体请查看super。
include
include 可引入其他的模板,可以在多模板之间共享一些小模板,如果某个模板已使用了继承那么 include 将会非常有用。
{'% include "item.html" %'}
可在循环中引入模板
{'% for item in items %'}
{'% include "item.html" %'}
{'% endfor %'}
这一点可以帮助我们把模板切分成更小的部分,从而使得在浏览器上,当我们需要改变页面时,我们可以渲染这些小部分的模板,而非一整个的大的模板。
include 可以接受任意表达式,只要它最终返回一个字符串或是模板所编译成的对象: {'% include name + ".html" as obj %'}.
在某些情况下,我们可能希望在模板文件不存在时不要抛出异常。对于这类情况,我们可以使用ignore missing来略过这些异常:
{'% include "missing.html" ignore missing %'}
被包含的模版自身可以扩展(extends)另一个模版(因此你可以让一系列相关联的模版继承同一种结构)。 一个被包含的模版并不会改变包含它的模版的区块结构,它有一个分离的继承树和块级命名空间。换言之, 在渲染时,include并不是 将被包含模版代码拉取到包含它的模版中的预处理器。相对的,它对被 包含的模版触发了一次的分离渲染,然后再将渲染的结果引入。
import
import 可加载不同的模板,可使你操作模板输出的数据,模板将会输出宏 (macro) 和在顶级作用域进行的赋值 (使用 set)。
被 import 进来的模板没有当前模板的上下文,所以无法使用当前模板的变量,
创建一个叫 forms.html 如下所示
{'% macro field(name, value='', type='text') %'}
<div class="field">
<input type="{'{ type }'}" name="{'{ name }'}"
value="{'{ value | escape }'}" />
</div>
{'% endmacro %'}
{'% macro label(text) %'}
<div>
<label>{'{ text }'}</label>
</div>
{'% endmacro %'}
我们可以 import 这个模板并将模板的输出绑定到变量 forms 上,然后就可以使用这个变量了:
{'% import "forms.html" as forms %'}
{'{ forms.label('Username') }'}
{'{ forms.input('user') }'}
{'{ forms.label('Password') }'}
{'{ forms.input('pass', type='password') }'}
也可以使用 from import 从模板中 import 指定的值到当前的命名空间:
{'% from "forms.html" import input, label as description %'}
{'{ description('Username') }'}
{'{ input('user') }'}
{'{ description('Password') }'}
{'{ input('pass', type='password') }'}
import 可以接受任意表达式,只要它最终返回一个字符串或是模板所编译成的对象: {'% import name + ".html" as obj %'}.
raw
如果你想输出一些 Nunjucks 特殊的标签 (如 {'{),可以使用 {'{),可以使用 {'% raw %'} 将所有的内容输出为纯文本。
filter
filter区块允许我们使用区块中的内容来调用过滤器。不同于使用|语法,它会将区块渲染出的内容传递给过滤器。
{'% filter title %'}
may the force be with you
{'% endfilter %'}
{'% filter replace("force", "forth") %'}
may the force be with you
{'% endfilter %'}
切记:你不能在这些区块中进行任何异步操作。
call
call区块允许你使用标签之间的内容来调用一个宏。这在你需要给宏传入大量内容时是十分有用的。在宏中,你可以通过caller()来获取这些内容。
{'% macro add(x, y) %'}
{'{ caller() }'}: {'{ x + y }'}
{'% endmacro%'}
{'% call add(1, 2) -%'}
The result is
{'%- endcall %'}
上面的例子将会输出"The result is: 3"。
关键字参数
jinja2 使用 Python 的关键字参数,支持函数,过滤器和宏。Nunjucks 会通过一个调用转换 (calling convention) 来支持。
关键字参数如下:
{'{ foo(1, 2, bar=3, baz=4) }'}
bar 和 baz 为关键字参数,Nunjucks 将他们转换成一个对象作为最后一个参数传入,等价于 javascript 的如下调用:
foo(1, 2, { bar: 3, baz: 4})
因为这使一个标准的调用转换,所以适用于所有的符合预期的函数和过滤器。查看 API 章节获得更多信息。
定义宏的时候也可以使用关键字参数,定义参数值时可设置默认值。Nunjucks 会自动将关键字参数与宏里定义的值做匹配。
{'% macro foo(x, y, z=5, w=6) %'}
{'{ x }'}, {'{ y }'}, {'{ z }'}, {'{ w}'}
{'% endmacro %'}
{'{ foo(1, 2) }'} -> 1, 2, 5, 6
{'{ foo(1, 2, w=10) }'} -> 1, 2, 5, 10
在宏中还可以混合使用位置参数 (positional arguments) 和关键字参数。如示例,你可以将位置参数用作关键字参数:
{'{ foo(20, y=21) }'} -> 20, 21, 5, 6
你还可以用位置参数来替换关键字参数:
{'{ foo(5, 6, 7, 8) }'} -> 5, 6, 7, 8
如下示例,你可以跳过 ("skip") 位置参数:
{'{ foo(8, z=7) }'} -> 8, , 7, 6
注释
你可以使用 来写注释,渲染时将会去除所有的注释。
{'% for user in users %'}...{'% endfor %'}
空白字符控制
模板在正常情况会将变量 (variable) 和标签区块 (tag blocks) 周围的空白字符完全输出。有时,你不想输出一些额外的空白字符,但代码又需要一些空白字符来显得整洁。
你可以在开始和结束区块 (start or end block tag) 添加 (-) 来去除前面和后面的空白字符。
{'% for i in [1,2,3,4,5] -%'}
{'{ i }'}
{'%- endfor %'}
上面准确的输出为 "12345",-%'} 会去除标签右侧的空白字符,{'%- 会去除标签之前的空白字符。
表达式
你可以使用和 javascript 一样的字面量。
- Strings: "How are you?", 'How are you?'
- Numbers: 40, 30.123
- Arrays: [1, 2, "array"]
- Dicts: { one: 1, two: 2 }
- Boolean: true, false
运算 (Math)
Nunjucks 支持运算 (但尽量少用,把逻辑放在代码中),可使用以下操作符:
- Addition: +
- Subtraction: -
- Division: /
- Division and integer truncation: //
- Division remainder: %
- Multiplication: *
- Power: **
可以如下使用:
{'{ 2 + 3 }'} (outputs 5)
{'{ 10/5 }'} (outputs 2)
{'{ numItems*2 }'}
比较 (Comparisons)
- ==
- ===
- !=
- !==
- >
- >=
- <
- <=
Examples:
{'%-` 会去除标签之前的空白字符。
## 表达式
你可以使用和 javascript 一样的字面量。
* Strings: `"How are you?"`, `'How are you?'`
* Numbers: `40`, `30.123`
* Arrays: `[1, 2, "array"]`
* Dicts: `{ one: 1, two: 2 }`
* Boolean: `true`, `false`
### 运算 (Math)
Nunjucks 支持运算 (但尽量少用,把逻辑放在代码中),可使用以下操作符:
* Addition: `+`
* Subtraction: `-`
* Division: `/`
* Division and integer truncation: `//`
* Division remainder: `%`
* Multiplication: `*`
* Power: `**`
可以如下使用:
```jinja
{'{ 2 + 3 }'} (outputs 5)
{'{ 10/5 }'} (outputs 2)
{'{ numItems*2 }'}Examples:
{'% if numUsers < 5 %'}...{'% endif %'}
{'% if i == 0 %'}...{'% endif %'}
Logic
- and
- or
- not
- 可使用大括号来分组
Examples:
{'% if users and showUsers %'}...{'% endif %'}
{'% if i == 0 and not hideFirst %'}...{'% endif %'}
{'% if (x < 5 or y < 5) and foo %'}...{'% endif %'}
If 表达式
和 javascript 的三元运算符 (ternary operator) 一样,可使用 if 的内联表达式:
{'{ "true" if foo else "false" }'}
当 foo 为 true 的时候最终输出 "true" 否则为 "false",对于获取默认值的时候非常有用:
{'{ baz(foo if foo else "default") }'}
函数调用 (Function Calls)
如果你传入一个函数,则可以直接调用
{'{ foo(1, 2, 3) }'}
正则表达式
你可以像在JavaScript中一样创建一个正则表达式:
{'{ /^foo.*/ }'}
{'{ /bar$/g }'}
正则表达式所支持的标志如下。查阅Regex on MDN以获取更多信息。
- g: 应用到全局
- i: 不区分大小写
- m: 多行模式
- y: 粘性支持(sticky)
自动转义 (Autoescaping)
如果在环境变量中设置了 autoescaping,所有的输出都会自动转义,但可以使用 safe 过滤器,Nunjucks 就不会转义了。
{'{ foo }'} // <span%gt;
{'{ foo | safe }'} // <span>
如果未开启 autoescaping,所有的输出都会如实输出,但可以使用 escape过滤器来转义。
{'{ foo }'} // <span>
{'{ foo | escape }'} // <span>
全局函数 (Global Functions)
以下为一些内置的全局函数
range([start], stop, [step])
如果你需要遍历固定范围的数字可以使用 range,start (默认为 0) 为起始数字,stop 为结束数字,step 为间隔 (默认为 1)。
{'% for i in range(0, 5) -%'}
{'{ i }'},
{'%- endfor %'}
上面输出 0,1,2,3,4.
cycler(item1, item2, ...itemN)
cycler 可以循环调用你指定的一系列的值。
{'% set cls = cycler("odd", "even") %'}
{'% for row in rows %'}
<div class="{'{ cls.next() }'}">{'{ row.name }'}</div>
{'% endfor %'}
上面的例子中奇数行的 class 为 "odd",偶数行的 class 为 "even"。你可以使用current属性来获取当前项(在上面的例子中对应cls.current)。
joiner([separator])
当合并多项的时候,希望在他们之间又分隔符 (像逗号),但又不希望第一项也输出。joiner 将输出分割符 (默认为 ",") 除了第一次调用。
{'% set comma = joiner() %'}
{'% for tag in tags -%'}
{'{ comma() }'} {'{ tag }'}
{'%- endfor %'}
如果 tags 为 ["food", "beer", "dessert"], 上面将输出 food, beer, dessert。
内置的过滤器
Nunjucks已经实现了jinja中的大部分过滤器,同时也新增了一些属于自己的过滤器。 我们需要为这些过滤器编写文档。下面是一部分过滤器的文档,其他的你可以点击链接查看jinja上的文档。
default(value, default, [boolean])
(简写为 d)
如果value全等于undefined则返回default,否则返回value。 如果boolean为true,则会在value为JavaScript中的假值时(比如:false, ""等)返回default。
在2.0版本中,这个过滤器的默认表现与以前有所不同。在之前的版本中,它会把boolean的默认值 设置为true,所以传入任何假值都会返回default。在2.0中,默认只有值为undefined时会 返回default。如果你仍旧希望保持原来版本的表现的话,你可以给boolean传入true,或是 直接使用value or default。
sort(arr, reverse, caseSens, attr)
用JavaScript中的arr.sort函数排序arr。如果reverse为true,则会返回相反的 排序结果。默认状态下排序不会区分大小写,但你可以将caseSens设置为true来让排序 区分大小写。我们可以用attr来指定要比较的属性。
striptags (value, [preserve_linebreaks])
类似于jinja中的striptags. 如果preserve_linebreaks为false(同时也是默认值),则会移去SGML/XML标签并用一个空格符 替换临近的、连续的空白符号。如果preserve_linebreaks为true,则会尝试保留临近的空白符号。 如果你希望使用管道操作符进行类似于{'{ text | striptags | nl2br }'}这样的操作时,你就会 需要用到后一种。否则你还是应该使用默认的用法。
dump (object)
在一个对象上调用JSON.stringify,并将结果输出到模板上。这在调试时很有用:{'{ foo | dump }'}。
其他过滤器
- abs
- batch
- capitalize
- center
- dictsort
- escape (简写为e)
- float
- first
- groupby
- indent
- int
- join
- last
- length
- list
- lower
- random
- rejectattr (只接受单个参数)
- replace (第一个参数也可以接受 JavaScript中的正则表达式)
- reverse
- round
- safe
- selectattr (只接受单个参数)
- slice
- string
- sum
- title
- trim
- truncate
- upper
- urlencode
- urlize
- wordcount
你也可以直接看代码。
mime
Comprehensive MIME type mapping API based on mime-db module.
MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。(百度百科)
简单点说,mime是一个互联网标准,通过设定它就可以设定文件在浏览器的打开方式。(有兴趣的话,可以试一下用css打开方式打开html文件,更助于理解)
Install
Install with npm:
npm install mime
Contributing / Testing
npm run test
Command Line
mime [path_string]
E.g.
> mime scripts/jquery.js
application/javascript
API - Queries
mime.lookup(path)
Get the mime type associated with a file, if no mime type is found application/octet-stream is returned. Performs a case-insensitive lookup using the extension in path (the substring after the last '/' or '.'). E.g.
var mime = ; mime; // => 'text/plain' mime; // => 'text/plain' mime; // => 'text/plain' mime; // => 'text/html' mime.default_type
Sets the mime type returned when mime.lookup fails to find the extension searched for. (Default is application/octet-stream.)
mime.extension(type)
Get the default extension for type
mime; // => 'html' mime; // => 'bin' mime.charsets.lookup()
Map mime-type to charset
mimecharsets; // => 'UTF-8' (The logic for charset lookups is pretty rudimentary. Feel free to suggest improvements.)
API - Defining Custom Types
Custom type mappings can be added on a per-project basis via the following APIs.
mime.define()
Add custom mime/extension mappings
mime; mime; // => 'text/x-some-format' The first entry in the extensions array is returned by mime.extension(). E.g.
mime; // => 'x-sf' mime.load(filepath)
Load mappings from an Apache ".types" format file
mime;The .types file format is simple - See the types dir for examples.
mz
mz包,通过require('mz/fs');导入。mz提供的API和Node.js的fs模块完全相同,但fs模块使用回调,而mz封装了fs对应的函数,并改为Promise。这样,我们就可以非常简单的用await调用mz的函数,而不需要任何回调。
Modernize node.js to current ECMAScript specifications! node.js will not update their API to ES6+ for a while. This library is a wrapper for various aspects of node.js' API.
Installation and Usage
Set mz as a dependency and install it.
npm i mzThen prefix the relevant require()s with mz/:
var fs = fsWith ES2017, this will allow you to use async functions cleanly with node's core API:
const fs = { if await fs // do something }Promisification
Many node methods are converted into promises. Any properties that are deprecated or aren't asynchronous will simply be proxied. The modules wrapped are:
- child_process
- crypto
- dns
- fs
- readline
- zlib
var exec = exec Promise Engine
mz uses any-promise.
mysql
- Install
- Introduction
- Contributors
- Sponsors
- Community
- Establishing connections
- Connection options
- SSL options
- Terminating connections
- Pooling connections
- Pool options
- Pool events
- Closing all the connections in a pool
- PoolCluster
- PoolCluster options
- Switching users and altering connection state
- Server disconnects
- Performing queries
- Escaping query values
- Escaping query identifiers
- Preparing Queries
- Custom format
- Getting the id of an inserted row
- Getting the number of affected rows
- Getting the number of changed rows
- Getting the connection ID
- Executing queries in parallel
- Streaming query rows
- Piping results with Streams2
- Multiple statement queries
- Stored procedures
- Joins with overlapping column names
- Transactions
- Timeouts
- Error handling
- Exception Safety
- Type casting
- Connection Flags
- Debugging and reporting problems
- Contributing
- Running tests
- Todo
sequelize
ws
- Protocol support
- Installing
- API docs
- WebSocket compression
- Usage examples
- Error handling best practices
- FAQ
- Changelog
- License
ws is a simple to use, blazing fast, and thoroughly tested WebSocket client and server implementation.
Passes the quite extensive Autobahn test suite. See http://websockets.github.io/ws/ for the full reports.
Note: This module does not work in the browser. The client in the docs is a reference to a back end with the role of a client in the WebSocket communication. Browser clients must use the nativeWebSocket object.
Protocol support
- HyBi drafts 07-12 (Use the option protocolVersion: 8)
- HyBi drafts 13-17 (Current default, alternatively option protocolVersion: 13)
Installing
npm install --save wsOpt-in for performance and spec compliance
There are 2 optional modules that can be installed along side with the ws module. These modules are binary addons which improve certain operations. Prebuilt binaries are available for the most popular platforms so you don't necessarily need to have a C++ compiler installed on your machine.
- npm install --save-optional bufferutil: Allows to efficiently perform operations such as masking and unmasking the data payload of the WebSocket frames.
- npm install --save-optional utf-8-validate: Allows to efficiently check if a message contains valid UTF-8 as required by the spec.
API docs
See /doc/ws.md for Node.js-like docs for the ws classes.
WebSocket compression
ws supports the permessage-deflate extension which enables the client and server to negotiate a compression algorithm and its parameters, and then selectively apply it to the data payloads of each WebSocket message.
The extension is disabled by default on the server and enabled by default on the client. It adds a significant overhead in terms of performance and memory comsumption so we suggest to enable it only if it is really needed.
The client will only use the extension if it is supported and enabled on the server. To always disable the extension on the client set the perMessageDeflate option to false.
const WebSocket = ; const ws = 'ws://www.host.com/path' perMessageDeflate: false;Usage examples
Sending and receiving text data
const WebSocket = ; const ws = 'ws://www.host.com/path'; ws; ws;Sending binary data
Server example
Broadcast example
ExpressJS example
const express = ;
const http = ;
const url = ;
const WebSocket = ;
const app = ;
app;
const server = http;
const wss = server ;
wss;
server;
echo.websocket.org demo
Other examples
Error handling best practices
// If the WebSocket is closed before the following send is attempted ws; // Errors (both immediate and async write errors) can be detected in an optional // callback. The callback is also the only way of being notified that data has // actually been sent. ws; // Immediate errors can also be handled with `try...catch`, but **note** that // since sends are inherently asynchronous, socket write failures will *not* be // captured when this technique is used. try ws; catch e /* handle error */ FAQ
How to get the IP address of the client?
The remote IP address can be obtained from the raw socket.
const WebSocket = ;
const wss = port: 8080 ;
wss;
When the server runs behing a proxy like NGINX, the de-facto standard is to use the X-Forwarded-For header.
wss;How to detect and close broken connections?
Sometimes the link between the server and the client can be interrupted in a way that keeps both the server and the client unware of the broken state of the connection (e.g. when pulling the cord).
In these cases ping messages can be used as a means to verify that the remote endpoint is still responsive.
const WebSocket = ; const wss = port: 8080 ; { thisisAlive = true;} wss; const interval = ;Pong messages are automatically sent in reponse to ping messages as required by the spec.
Changelog
We're using the GitHub releases for changelog entries.
bluebird
cheerio
const cheerio = const $ = cheerio text'Hello there!' $//=> <h2 class="title welcome">Hello there!</h2>